Notatki 3sky'a

Minimalistyczny blog o TI.

Gitlab - szablony

2021-06-15 5 min czytania 3sky

Witam

Praca jako konsultant jest cieżka. Masz tak dużo zadań do zrobenia. Dzień jest odrobine za krótki, ale możliwości do nauki są fantastyczne. Dziś mam troszkę czasu, aby zebrać moje notatki o GitLab, głównie części CI. Z drugiej strony jako repozutorium kodu dla organizacji, GitLab jest moim ulubionym rozwiązaniem. Szybki, łatwy w użyciu, macierz dostępów, oraz zarządzanie kluczami wdrażającymi. Wszystkie te rzeczy są super. Ogólnie to nie jest marketingowy wpis, ale mógłby :)

GitLabCI

GitLabCi jest bardzo popularny ze względu na łatwość użycia oraz szybkość configuracji, dodatkowo jest za darmo - oczywiście jeżeli nie zostanie przekroczony darmowy limi (400 minut CI/CD miesięcnzie). Najważniejszym elementem jest tutaj konfiguracja początkowa. Ogranicza się ona do jednego pliku .gitlab-ci.yml, umieszczonego w głównym folderze repozytorium.

Problem

W tym artykule nie ma problemów. To będzie tekst o szablonach, oraz o tym czemu są potrzebne. Załózmy, że mamy prosty przepływ wdrażający aplikacjię na Maszynę Wirtualną. Zwróć uwage na komentarze w kodzie.

image: ubuntu:latest

before_script:
    - apt update
    - apt install openssh-client -y
    - eval $(ssh-agent -s)
    # base64 is very usefull in case of id_rsa formating
    - ssh-add <(echo "$DEPLOYER_PRIVATE_KEY" | base64 -d)
    - mkdir -p ~/.ssh
    - 'echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'

stages:
    # order of stages is important and is configure here
    - create_env
    - build
    - deploy

# now we need to build .env file for compilation time
# that's tag driven deployment, so we deploy to particular
# env based on tag

build_the_dotfile_dev:
    stage: create_env
    script:
      - echo "$DOTFILE_DEV" > .env
    artifacts:
      paths:
        - .env
    rules:
      - if: '$CI_COMMIT_TAG =~ /^development/'

build_the_dotfile_stg:
    stage: create_env
    script:
      - echo "$DOTFILE_STG" > .env
    artifacts:
      paths:
        - .env
    rules:
      - if: '$CI_COMMIT_TAG =~ /^staging/'

build_the_dotfile_prod:
    stage: create_env
    script:
      - echo "$DOTFILE_PROD" > .env
    artifacts:
      paths:
        - .env
    rules:
      - if: '$CI_COMMIT_TAG =~ /^production/'

# now we build our app
# this step is generic
build_the_app:
    image: node:12-buster
    stage: build
    script:
        - git checkout -b '$CI_COMMIT_TAG'
        - yarn install
        - NODE_ENV=production yarn build
        - tar --warning=no-file-changed --exclude=current.tar.gz --exclude-vcs -zcf current.tar.gz ./ || [[ $? -eq 1 ]]
    artifacts:
      paths:
        - current.tar.gz
      expire_in: 2 hrs
    rules:
      - if: '$CI_COMMIT_TAG =~ /^development|^staging|^production/'

# logic is simple
# 1. we transfer our artifact to VM
# 2. run the deployment script with some mv/pm2 logic
# 3. environment based on TAG again
deploy_front_to_dev:
    stage: deploy
    script:
        - scp current.tar.gz deployer@$DEV_SERVER_IP:/home/appuser/front
        - ssh deployer@$DEV_SERVER_IP "bash /home/appuser/deploy.sh front $CI_COMMIT_TAG"
    rules:
      - if: '$CI_COMMIT_TAG =~ /^development/'

deploy_afront_to_stg:
    stage: deploy
    script:
        - scp current.tar.gz deployer@$STG_SERVER_IP:/home/appuser/front
        - ssh deployer@$STG_SERVER_IP "bash /home/appuser/deploy.sh front $CI_COMMIT_TAG"
    rules:
      - if: '$CI_COMMIT_TAG =~ /^staging/'

deploy_front_to_prod:
    stage: deploy
    script:
        - scp current.tar.gz deployer@$PROD_SERVER_IP:/home/appuser/front
        - ssh deployer@$PROD_SERVER_IP "bash /home/appuser/deploy.sh front $CI_COMMIT_TAG"
    rules:
      - if: '$CI_COMMIT_TAG =~ /^production/'

Całkiem miłe, niestety dość długie rozwiązanie - 83 linie kodu. Dodatkowo dotarcza zbyt wielu możliwości popełnienia drobnych błędów w pisowni.

Rozwiązanie

Tutaj wczodzą szablony. To coś w rodzaju obiektu, który możemy wywołać z naszego przepływu. Po dodaniu szablonów nasz kod wygląda nastepująco:

stages:
  - create_env
  - build
  - deploy

# note dot on the begining
# templates must start with dot, otherwise
# will be executed
.env_template: &envs
  stage: create_env
  image: alpine
  script:
    - echo "$DOTFILE" > .env
  artifacts:
      paths:
        - .env

.deploy_template: &deploy_tmpl
  stage: deploy
  image: ubuntu:latest
  before_script:
    - apt update
    - apt install openssh-client -y
    - eval $(ssh-agent -s)
    - ssh-add <(echo "$DEPLOYER_PRIVATE_KEY" | base64 -d)
    - mkdir -p ~/.ssh
    - 'echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
  script:
    - scp current.tar.gz deployer@$SERVER_IP:/home/appuser/front
    - ssh deployer@$SERVER_IP "bash /home/appuser/bin/deploy.sh front $CI_COMMIT_TAG"

build_the_dotfile_dev:
  variables:
    DOTFILE: "$DOTFILE_DEV"
  <<: *envs
  rules:
    - if: '$CI_COMMIT_TAG =~ /^development/'

build_the_dotfile_stg:
  variables:
    DOTFILE: "$DOTFILE_STG"
  <<: *envs
  rules:
    - if: '$CI_COMMIT_TAG =~ /^staging/'

build_the_dotfile_prod:
  variables:
    DOTFILE: "$DOTFILE_PROD"
  <<: *envs
  rules:
    - if: '$CI_COMMIT_TAG =~ /^production/'

build_the_app:
  image: node:12.22.1-buster
  stage: build
  before_script:
    - yarn policies set-version 1.22.10
  script:
    - git checkout -b '$CI_COMMIT_TAG'
    - yarn install
    - NODE_ENV=production yarn build
    - tar --warning=no-file-changed --exclude=current.tar.gz --exclude-vcs -zcf current.tar.gz ./ || [[ $? -eq 1 ]]
  artifacts:
    paths:
      - current.tar.gz
  rules:
    - if: '$CI_COMMIT_TAG =~ /^development|^staging|^production/'

deploy_front_to_dev:
  variables:
    SERVER_IP: "$DEV_SERVER_IP"
  <<: *deploy_tmpl
  rules:
    - if: '$CI_COMMIT_TAG =~ /^development/'

deploy_afront_to_stg:
  variables:
    SERVER_IP: "$STG_SERVER_IP"
  <<: *deploy_tmpl
  rules:
    - if: '$CI_COMMIT_TAG =~ /^staging/'

deploy_front_to_prod:
  variables:
    SERVER_IP: "$PROD_SERVER_IP"
  <<: *deploy_tmpl
  rules:
    - if: '$CI_COMMIT_TAG =~ /^production/'

Zdaję sobie sprawę z tego, że kod jest wciąż długi, ale logika jest juz w 1 miejscu, a nie 3. Dodatkowo zarządzanie środowiskami będzie w przyszłości łatwe i czystę. Możliwe jest również wykorzystanie szablonów jako oddzielnych importowalnych plików.

Testowanie

Kolejny ciekawi temat, ważne jest testowanie składni przed umieszczeniem kodu na środowisku deweloperskim. W tym celu wystarczy użyć łatwej metody to testowania, Twoich scen. Jedyną rzeczą jaką potrzebujesz jest uruchomienie poniższej komendy w Twoim localnym repozytorium.

docker run -d \
  --name gitlab-runner \
  --restart always \
  -v $PWD:$PWD \
  -v /var/run/docker.sock:/var/run/docker.sock \
  gitlab/gitlab-runner:latest

Następnie uruchom wybrane zadanie.

docker exec -it -w $PWD gitlab-runner gitlab-runner exec docker <task name>

Jest tutaj jeden problem który sprawia, że chce mi się płakać… Nie mogę znaleść opcji, aby urchomić cały przepływ. Uruchamianie scen jest w porządku, niesety nie ma opcji, aby przetestować pamięć podreczną, lub wymiane plików pomiędzy zadaniami.

Podsumowanie

Podsumowanie jest zbyteczne. Szabony są czytelne i łatwe w użyciu. GitLab jest świetny, niesety życie jest trudne. Mam 27 lat, wczoraj kupiłem sobie pierwszy kask rowerowy. Bezpieczeństwo przedewszystkim. Trzymaj się.