CI для фронтенда: Gitlab, Traefik, Docker

В каждом уважающем себя проекте должны участвовать QA инженеры. Ежедневно перед ними будет стоять задача проверки выполнения задач в отдельных ветках. Очень часто процесс перехода на нужную ветку, сборки и тестирования занимает много времени, к тому же, локально не всегда возможно полностью воссоздать максимально идентичное боевому окружение.

Цель данной статьи — показать простую технику настройки стенда на несколько веток. Данная статья написана разработчикам от разработчика, поэтому вряд ли будет представлять существенный интерес для профессиональных DevOps инженеров.

Требования:

  • Gitlab (bare metal/cloud)
  • Выделенный сервер
  • Свободный домен

Шаг первый: настройка Gitlab


  1. Установите Gitlab Runner на Вашем выделенном сервере
  2. Создайте воркера, поддерживающего сборки docker образов
  3. Включите Container Registry

Шаг второй: настройка сервера


  1. Установите Docker
  2. Установите Compose
  3. Создайте пользователя:

    # создаем пользователя для деплоя образов, разрешаем ему работу с докером
    $ sudo adduser deployer
    $ sudo groupadd docker
    $ sudo usermod -aG docker deployer
    
    # генерим ssh ключи
    $ su deployer
    $ ssh-keygen -t rsa
    
    (when asked for a passphrase, enter no passphrase)
    
    # разрешаем подключение к серверу с только что созданным ключом 
    $ cp ~/.ssh/id_rsa.pub ~/.ssh/authorized_keys
    

  4. Настройте traefik (сервер, проксирующий запросы к Docker контейнерам на базе их ярлыков(лейблов)):

    $ sudo mkdir -p /opt/traefik
    $ docker network create web
    # файлы настроек docker-compose & traefik упрощены до предела и должны быть достаточно понятными
    

    /opt/traefik/docker-compose.yml

    version: '2'
    
    services:
      traefik:
        image: traefik:1.4.6
        restart: always
        ports:
          - 80:80
        networks:
          - web
        volumes:
          - /var/run/docker.sock:/var/run/docker.sock
          - ./traefik.toml:/traefik.toml
        container_name: traefik
    
    networks:
      web:
        external: true

    /opt/traefik/traefik.toml

    Замените DOMAIN.COM на свой домен.

    debug = true
    checkNewVersion = true
    logLevel = "ERROR"
    defaultEntryPoints = ["http"]
    
    [entryPoints]
      [entryPoints.http]
      address = ":80"
    
    [retry]
    
    [docker]
    endpoint = "unix:///var/run/docker.sock"
    domain = "DOMAIN.COM"
    watch = true
    exposedbydefault = false
    

  5. Запустите traefik:

    cd /opt/traefik && docker-compose up -d

  6. Добавьте A запись для DOMAIN.COM вида '*' — IP выделенного сервера.

Шаг третий: подготовка репозитория


  1. Добавьте в корень репозитория Dockerfile:

    FROM node:8.9 as build-deps
    WORKDIR /usr/src/app
    COPY package.json ./
    RUN npm i
    COPY . ./
    RUN npm run build
    
    FROM nginx:1.12-alpine
    COPY --from=build-deps /usr/src/app/dist /usr/share/nginx/html
    EXPOSE 80
    CMD ["nginx", "-g", "daemon off;"]
    

    Измените

    npm run build

    на вашу команду сборки. Аналогично отредактируйте путь

    /usr/src/app/dist

    изменив dist на вашу директорию сборки.
    Подробнее почитать про структуру и команды докерфайа можно тут.
  2. Добавьте .gitlab-ci.yml файл с секцией сборки образа:

    image: docker:stable
    
    variables:
      DOCKER_HOST: tcp://docker:2375/
      DOCKER_DRIVER: overlay2
      GITLAB_DOMAIN: gitlab.com
    services:
      - docker:dind
    
    stages:
      - build
    
    build_staging:
      stage: build
      script:
        - export GITLAB_DOMAIN=gitlab.com
        - export CONTAINER_IMAGE=$GITLAB_DOMAIN/frontend/main/image
        - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $GITLAB_DOMAIN
        - docker pull $CONTAINER_IMAGE:$CI_COMMIT_REF_SLUG || true
        - export URL="Host:${CI_COMMIT_REF_NAME}.tests.DOMAIN.COM"
        - docker build -t $CONTAINER_IMAGE:$CI_COMMIT_REF_SLUG . --label "traefik.backend=${CI_COMMIT_REF_NAME}" --label "traefik.frontend.rule=${URL}"
        - docker push $CONTAINER_IMAGE:$CI_COMMIT_REF_SLUG
    
  3. Добавьте приватный ключ deployer@DOMAIN.COM в настройки репозитория(Settings -> CI/CD -> Variables) как SSH_PRIVATE_KEY
  4. Добавьте deploy секции:

    stages:
      - build
      - deploy
    /// ....
    deploy_staging:
      stage: deploy
      image: kroniak/ssh-client:3.6
      script:
        - mkdir ~/.ssh
        - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
        - eval $(ssh-agent -s)
        - ssh-add <(echo "$SSH_PRIVATE_KEY")
        - ssh deployer@DOMAIN.COM "docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN $GITLAB_DOMAIN"
        - ssh deployer@DOMAIN.COM "docker stop frontend_${CI_COMMIT_REF_SLUG}" || true
        - ssh deployer@DOMAIN.COM "docker rm frontend_${CI_COMMIT_REF_SLUG}" || true
        - ssh deployer@DOMAIN.COM "docker rmi GITLAB-DOMAIN.COM/frontend/main/image:${CI_COMMIT_REF_SLUG}" || true
        - ssh deployer@DOMAIN.COM "docker run --name frontend_${CI_COMMIT_REF_SLUG} --network=web -d $GITLAB_DOMAIN/frontend/main/image:${CI_COMMIT_REF_SLUG}"
      environment:
        name: review/$CI_COMMIT_REF_NAME
        url: http://${CI_COMMIT_REF_NAME}.tests.DOMAIN.COM
        on_stop: stop_staging
    
    stop_staging:
      stage: deploy
      image: kroniak/ssh-client:3.6
      variables:
        GIT_STRATEGY: none
      script:
        - mkdir ~/.ssh
        - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
        - eval $(ssh-agent -s)
        - ssh-add <(echo "$SSH_PRIVATE_KEY")
        - ssh deployer@DOMAIN.COM "docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN GITLAB-DOMAIN.COM"
        - ssh deployer@DOMAIN.COM "docker stop frontend_${CI_COMMIT_REF_SLUG}" || true
        - ssh deployer@DOMAIN.COM "docker rm frontend_${CI_COMMIT_REF_SLUG}" || true
        - ssh deployer@DOMAIN.COM "docker rmi $GITLAB_DOMAIN/frontend/main/image:${CI_COMMIT_REF_SLUG}" || true
      when: manual
      environment:
        name: review/$CI_COMMIT_REF_NAME
        action: stop

Приведенных выше шагов достаточно для получения следующего результата:

  • Каждый новый коммит в ветку иницирует сборку и развертку последнего актуального кода по адресу %branch_name%.tests.DOMAIN.COM
  • В Gitlabе включен механизм окружений, позволяющий в пару касаний создавать/удалять/открывать задеплоенные образы

Следующие шаги:

  • Настроить отдельную секцию для сборок мастера. В случае мастера разумно тэгать образ через COMMIT_SHA
  • Добавить конфиги к nginxу в докере
  • Пробросить параметры сборки бандла через ARG и ENV механизмы докерфайла
  • Настроить использование кэша из образа для сборок

За основу взята статья.
Поделиться публикацией

Комментарии 10

    0
    Сам сейчас решаю подобную задачу.
    Скажите, Gitlab вы ставите прямо на систему или запускаете через докер? Если первый вариант, то почему именно он?
    Как вы настраиваете https для него? Насколько я понимаю, Gitlab registry без https работать не будет.
      0
      registry работать будет. У меня оно вообще работает на нестандартном порту на http. Сам gitlab в виде omnibus сборки локально на железе стоит. Почему? Причин две — я ленивый, и есть возможность позволить себе отдельный сервак на это
      0
      Спасибо за статью,
      Но для совсем далекого от devops проясните, что прописать в traefik для локальной работы?
      endpoint = unix:///var/run/docker.sock берет все докер контейнеры, а как к ним теперь обращаться?
        0
        Для этого контейнерам надо прописать labelы, и traefik сам поймет как до них роутить. Или о чем речь?
          0
          Все верно, спасибо. В статье о label только в ci части, потому и уточнил.
          А можно его уже на продакшене использовать?
          И можно ли использовать вместо nginx?
          У меня сейчас несколько микросервисов, и работают они через nginx, и наверное label можно напрямую сервису присвоить минуя nginx.
            0
            Да, можно, но не советовал бы.
            В проде для таких целей лучше подойдет связка из Consul + Nginx + DNS.
            В локальном dns(bind) какой-нибудь, говорите, что зона *.service.consul сервится консулом на таком-то порту, консул отдает вам айпишник какого-то из серверов, на котором в консулом же сгенеренных конфигах прописаны адреса микросервисов + настроена балансировка
              0
              Я использовал ранее kong, но мне показалось traefik более прост в использовании.
              А почему на проде не советовали бы?
                0
                никаких объективных причин, только субъективные. nginx показал себя как хороший мощный сервер, который может все. Эта штука не такая гибкая
        0
        Благодарю за статью. Однозначно в закладки.
          +1
          Что будет происходить с запущенными контейнерами, в ветку которых больше не будет происходить коммитов? Будут оставаться запущенными и копиться, пока память на сервере не кончится?

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое