Jednym z największych prezentów jaki może zrobić sobie deweloper to automatyzacja czynności, które są czasochłonne, powtarzalne i po prostu nudne. Do takich czynności na pewno zalicza się tzw deploy, czyli wstawienie swojej produkcyjnej wersji strony czy apki na serwer docelowy.

Większość polskich jak i międzynarodowych serwerów nie daje możliwości konfiguracji tzw Continuous Integration / Continuous Development, jednakże z pomocą przychodzi nam GitLab i jego usługa CI / CD Pipelines.

Za jej pomocą możemy wypychać nasze zmiany bezpośrednio do każdego serwera, o ile mamy dostęp SSH. Dzięki temu nie musimy tracić czasu na wpisywanie tych samych komend w kółko czy co chwile przerzucać naszą produkcyjną apke przez ftp do serwera docelowego.

Konfiguracja projektu

Co potrzebujemy do rozpoczęcia naszego własnego Continuous Integration ?

  1. Repozytorium na GitLabie, które będziemy chcieli wypychać z każdym git push'em.
  2. Nowy klucz SSH, klucz jest potrzebny do komunikacji pomiędzy GitLab'em a zewnętrznym serwerem. Ważne aby klucz nie był zabezpieczony hasłem, gdyż nie mamy bezpośredniego dostępu do konsoli task runner'a.
  3. Skonfigurowany serwer docelowy, szczególnie chodzi tutaj o przygotowanie folderu z którym będziemy synchronizować zmiany, oraz dodanie klucza ssh.

Konfiguracja pliku gitlab-ci.yaml

Gitlab task runner jest sterowany przez plik gitlab.ci.yaml, w którym ustalamy zasady oraz potrzebne komendy do naszego systemu ciągłej integracji.

Warto przemyśleć swoja strategie CI, task runner przestaje działać jeżeli jakaś komenda zawiedzie lub zakończy się nie powodzeniem.

Co możemy zrobić z task runnerem ?

  • Egzekucja dowolnego obrazu (image) Docker.
  • Egzekucja (image) Docker i wykonywanie zadania przez SSH.
  • Łączenie ze zdalnym serwerem SSH.
  • Bash, Windows Batch i Windows PowerShell.
  • Automatyczne ładowanie konfiguracji bez ponownego uruchomienia.
  • Buforowanie kontenerów Docker.
  • Łatwa instalacja dla GNU / Linux, MacOS i Windows.
  • Wbudowany serwer HTTP metryk Prometheus

Ja osobiście stosuje najczęściej 2 strategie:

Docelowy serwer jest zgodny z aplikacją (np. node.js). Task runner synchronizuje zawartość repozytorium z serwerem docelowym za pomocą SSH, następnie odpalamy serie komend potrzebnych do uruchomienia instalacji, np (test, build, app restart).


before_script:
  - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client git -y )'
  - apt-get update -qq && apt-get install -y -qq sshpass
  - apt-get --yes --force-yes install git ssh rsync
  - eval $(ssh-agent -s)
  - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
  - mkdir -p ~/.ssh
  - chmod 700 ~/.ssh


rspec:
  script:
    - sshpass -V
    - export SSHPASS=$SSH_Key
    - rsync -avz -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" --progress . user@serwer.net:/sciezka/do/folderu/
    - ssh -o stricthostkeychecking=no user@serwer.net 'npm install --prefix '/sciezka/do/folderu/'
    - ssh -o stricthostkeychecking=no user@serwer.net 'app restart nazwa.apki'


Docelowy serwer jest nie zgodny z naszą aplikacją, to sytuacja w której chcielibyśmy na przykład synchronizować naszą apke opartą o node.js,  taką jak generator stron statycznych (gatsby.js) albo CRA z wszechobecnym hostingiem php.  Wtedy najlepiej zbudować cała aplikację po stronie task runnera a następnie za pomocą ssh użyć komendy rsync aby przekopiować gotowe pliki na serwer docelowy.

image: node:10
cache:
  paths:
    - node_modules/


before_script:
  - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client git -y )'
  - 'which rsync || ( apt-get update -y && apt-get install rsync -y )'
  - eval $(ssh-agent -s)
  - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
  - mkdir -p ~/.ssh
  - chmod 700 ~/.ssh

rspec:
  script:
    - npm install gatsby-cli -g
    - npm install
    - gatsby build
    - rsync -avz -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" . user@serwerdocelowy.net:/sciezka/do/folderu/docelowego
    

Konfiguracja GitLaba

Aby cały proces mógł być możliwy musimy stworzyć pipeline job w sekcji naszego projektu, pod zakładką CI / CD.

Jak widać po plikach konfiguracyjnych, w obydwu przypadkach używamy klucza SSH aby połączyć się z naszym serwerem docelowym . Ponieważ tak jak pisałem wyżej, podczas egzekucji task runnera, nie mamy możliwości wpisania hasła do naszego serwera, musimy wytworzyć Klucz ssh i klucz prywatny  na serwerze docelowym następnie umieszczamy go jako "Environment variables " w ustawieniach CI / CD gitLaba.

Podczas początkowej konfiguracji skryptu (before script) dodajemy klucze do ustawień obrazu docker, dzięki, któremu utworzymy bezpieczne połączenie pomiędzy serwerami.

Git Lab Setting CI / CD environment variables 

Końcowe Uwagi

Ja w wyżej wymienionych plikach używam operacji "rsync", która wyszukuje zmiany w plikach i kopiuje tylko te nowe, jest to szybsze rozwiązanie niż ewentualna alternatywa "scp", która bardziej przypomina zwykłe kopiowanie.  

Git Lab w wersji darmowej daje nam 2000 min pracy task runnera w miesiącu, warto pamiętać o tym i zaplanować swój proces "deploymentu" odpowiednio.

Prawdopodobnie task runner nie jest tak wydajny jak twój komputer, więc budowa aplikacji może potrwać pare minut. Szybko może okazać się że limit 2000 min to tak naprawdę bardzo mało czasu, jeżeli uruchamiamy task runnera co chwile z każdym "puszem" do naszego repozytorium.  

Dodatkowo GitLab daje nam możliwość podłączenia web hooków i custom triggers'ów pod nasze pipelines'y. Może się przydać do uruchomienia procesu CI np. z instalacji Wordpress albo nawet za pomocą zwykłej komendy curl.

Więcej info na na temat GitLaba task runnera na oficjalnej dokumentacji strony.  

Pozdrawiam.

SpaceGhost