Действующие лица (Команда): разработчиков – 2 человека, админ – 1 человек.

Статья повествует об использовании таких технологий, как Ansible, Docker Swarm, Jenkins и Portainer для реализации CI/CD-пайплайна с возможностью контроля за ним с помощью красивого веб-интерфейса.



Вступление


Чего обычно хочет разработчик? Он хочет творить, не думая о деньгах, и максимально быстро видеть результаты собственного творчества.

С другой стороны, есть бизнес, который хочет денег, да побольше, и поэтому постоянно думает о снижении времени вывода продукта на рынок. Другими словами, бизнес мечтает об ускорении получения MVP (a.k.a. Minimum Viable Product) в новых продуктах или при обновлении существующих.

Ну а чего же хочет админ? А админ – человек простой, он хочет, чтобы сервис не падал и не мешал играть в Кваку Танки и чтобы его пореже дергали разработчики и бизнес.
Поскольку для реализации желаний админа, как показывает правда жизни, его силами должны реализоваться и мечты других героев, представители ИТ-тусовки много работали над этим. Часто получалось достичь желаемого, придерживаясь методологии DevOps и реализуя принципы CI/CD (Continuous Integration and Delivery).

Так получилось в одном небольшом новом проекте в Уральской Дирекции ИТ, в которой удалось в весьма сжатые сроки реализовать полный пайплайн от публикации изменений исходников в системе контроля версии разработчиком до автоматического запуска новой версии приложения в тестовой среде.

Часть 0. Описание задачи


Архитектура системы


После некоторых обсуждений командой была выбрана следующая двухуровневая архитектура:

  • Бекэнд-часть на Java, реализованная на фреймворке Spring Boot, общающаяся с различными БД и другими корпоративными системами (потому что легко, быстро и понятно как писать).
  • Фронтэнд-часть на NodeJS (и ReactJS – интерфейс в браузере), потому что очень быстро работает.

В свою очередь, к этим компонентам был добавлен еще NGINX-сервер, являющийся фронтэндом для NodeJS-приложения. Его роль заключалась в распределении запросов между самим приложением и другими инфраструктурными компонентами системы, речь о которых пойдет ниже.

Чего хотелось команде


Как только новому проекту был дан зеленый свет, появилась первая техническая задача, а именно – подготовка «оборудования» для запуска нового проекта. Поскольку всем участникам было очевидно, что без максимальной оперативности выкатки новых версий на серверы развитие проекта будет весьма непростым, сразу же было решено идти по пути полного CI/CD, т.е. хотелось достичь следующего пайплайна:

  • Разработчик публикует изменения (коммит) в систему контроля версий (гит);
  • гит проводит минимально необходимое тестирование содержимого коммита на наличие необходимой атрибутики (например, правильный формат commit message), соответствие стилю оформления Банка и другой бюрократии;
  • гит-сервер посредством механизма web-hook-ов дергает сервер непрерывной интеграции Jenkins;
  • Jenkins запускает операции скачивания актуальной версии исходников из гит-а и выполнения CI/CD-пайплайна:

  1. компиляция исходников и первоначальное тестирование;
  2. сборки новой версии Docker-образов (неприлично же что-то деплоить в 2018-м на голое железо или виртуалку, не поймут);
  3. публикация образов в Artifactory (система хранения и управления бинарными артефактами, рекомендую!);
  4. перезапуск новой версии приложения (или всего «стека» приложений) на сервере с «откатом» к предыдущей версии в случае не самого успешного обновления.

Рамки


У людей в теме наверняка уже возник вопрос: «А чего это они какими-то костылями пользуются, а не 'production-ready'-решениями а-ля Kubernetes или Mesos / Marathon ?». Подобный вопрос вполне разумен, поэтому сразу же скажем, что описываемое решение было использовано по целому ряду причин, в том числе:

  • Оно проще (сильно-сильно проще);
  • Его было легче понять всей команде и развернуть админу.

Однако, мы не забываем, что выбранное нами решение относится к богатому семейству костылей, и надеемся в недалеком будущем переехать на более стандартный стек OpenShift + Bamboo.

Кроме того, эта статья касается только веб-ориентированных приложений, а также рассказывает об идеальном случае stateless-архитектуры, когда данные наверняка где-то есть, но они далеко, и мы об их хранении не задумываемся.

Часть 1. Установка и базовая настройка ПО на хост-системе


С целью максимальной автоматизации и высокой воспроизводимости всей цепочки хост-систему (виртуальная машина на базe VMWare / qemu KVM / облако / что-то еще) было решено настраивать с использованием системы управления конфигурацией Ansible.

Стоит добавить, что в дополнение к легкой повторяемости и воспроизводимости, использование подобных систем (кроме Ansible, существуют также системы Puppet и Chef) обладает огромным преимуществом перед использованием разнообразных shell- или python- скриптов в виде наличия идемпотентности, т.е. свойства, при котором при повторных запусках итоговое состояние системы не изменяется.

Данное преимущество вытекает из того факта, что при использовании систем управления конфигурациями описывается не процесс достижения желаемого состояния, а само желаемое состояние в декларативной форме.

1.1 ssh HostKeyChecking


По умолчанию, Ansible уважает безопасность и проверяет ssh-отпечаток удаленного конфигурируемого хоста. Т.к. в данном режиме отключена возможность авторизации по паролю, то при первоначальной настройке сервера требуется либо отключить HostKeyChecking, либо предварительно добавить отпечаток в локальный кеш. Последнего можно достичь целыми двумя путями:

или так:

Определить специальную переменную окружения:

$ export ANSIBLE_HOST_KEY_CHECKING=False

или по-другому:

Добавить в локальный конфигурационный файл ansible.cfg параметр host_key_checking:

[defaults]
host_key_checking = False

При первом способе проверка отключается только пока существует такая переменная окружения, а при втором – полностью для этого хоста.

1.2 Inventory


Inventory – это сущность в системе Ansible, с помощью которой описываются хосты и их группы, конфигурацией которых необходимо управлять.

Inventory можно описывать в формате ini или yaml. В данном проекте был выбран последний.

Пример файла hosts.yml:

#_ Группа all существует всегда
all:
  hosts:
    #	имя хоста удаленной системы,  к которому будет подключаться Ansible
    some-cool-vm-host
  vars:
    #	имя пользователя, кем будет авторизоваться
    ansible_user: 'root'
    # очень плохо такое делать, но тут записан пароль открытым текстом :-(
    ansible_password: '12345678'
 
    # Это сертификат банковского СА
    corp_ca_crt: "-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----"

Для впервые столкнувшихся с форматом yaml хотелось бы отметить, что все отступы в данном формате необходимо оформлять пробелами.

1.3 Playbook


Playbook – это еще одна сущность в Ansible, в которой непосредственно декларативно описывается желаемое конечное состояние хостов и групп из Inventory. Так же, как и почти все в Ansible, playbook описывается в файле (ах) в yaml-формате.

Для того чтобы запустить на выполнение playbook-файл, необходимо выполнить команду вида:

ansible-playbook -i ./hosts.yml tasks.yml

В данном playbook-е описывалась полная настройка базовой системы с созданием необходимых пользователей и установкой Docker-а:

#_ указывается маска имен хостов и групп, для которых будут выполняться нижеописанные таски (задания)
- hosts: all
  tasks:
  # Список тасков
    - name: Удаляем все репозитории из системы
      shell: rm /etc/zypp/repos.d/* || exit 0
 
    - name: Добавляем банковские SLES-репы на хост REPOs...
      zypper_repository: repo="{{ item.repo }}" name="{{ item.name }}" disable_gpg_check="{{ item.disable_gpg_check|default('no') }}"
      with_items:
        - { repo: "http://...", name: "SLE-DISTRO-X" }
 
    - name: Обновляем все пакеты в системе автоматически
      zypper: 
        name: '*'
        state: latest
 
    - name: Помещаем сертификат банковского CA на сервер
      copy:
        # Берем сертификат из переменных Inventory
        content: '{{ corp_ca_crt }}'
        dest: /etc/pki/trust/anchors/сorpCA.crt
        owner: root
        group: root
        mode: 0644
 
    - name: ... и перечитываем системное хранилище
      shell: update-ca-certificates
 
    - name: Создаем юзеров и группы
      group: name="{{ item.name  }}" gid={{ item.gid }} state="present"
      with_items:
        - { name: "docker", gid: 1000 }
    - user: 
        name: "{{ item.name }}"
        uid: "{{ item.uid }}" 
        group: "{{ item.gid  }}"
        state: "present"
      with_items:
        - { name: "dockeradm", uid: 1000, gid: "docker" }
 
    - name: Меняем пароли пользователям
      user: 
        name: "{{ item }}" 
        password: "$6$..."
        generate_ssh_key: yes
      with_items:
        - root
        - dockeradm
 
    - name: Добавляем публичный ssh-ключ текущего пользователя ЛОКАЛЬНОЙ машины указанным юзерам на УДАЛЕННОМ сервере
      authorized_key: 
        user: "{{ item }}" 
        key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
        state: "present" 
      with_items:
        - root
        - dockeradm
 
    - name: Создаем нужные VG в системе
      lvg:
        vg: 'vgAPP'
        pvs: '/dev/sdb'
 
    - name: ... и LV
      lvol: 
        vg: "{{ item.vg }}" 
        lv: "{{ item.lv }}"
        size: "{{ item.size }}"
      with_items:
        - { vg: 'vgAPP', lv: "lvData", size: "10G" }
        - { vg: 'vgAPP', lv: "lvDockerData", size: "5G" }
 
    - name: Создаем ФС на созданных LV-ах
      filesystem: dev="/dev/{{ item }}" fstype="btrfs"
      with_items:
        - 'vgAPP/lvData'
        - 'vgAPP/lvDockerData'
 
    - name: Прописываем информацию об ФС в /etc/fstab и монтируем их
      mount: path="{{ item.dst }}" src="/dev/{{ item.src }}" state="mounted" fstype="btrfs" opts="noatime"
      with_items:
        - { src: "vgAPP/lvData", dst: "/APP" }
        - { src: "vgAPP/lvDockerData", dst: "/var/lib/docker" }
 
    - name: Создаем всякие важные каталоги
      file: 
        path: "{{ item.path }}" 
        state: "directory" 
        # внимание не дефолтные значения
        mode: "0{{ item.perms|default('755') }}" 
        owner: "{{ item.user|default('dockeradm') }}" 
        group: "{{ item.group|default('docker') }}"
      with_items:
        - { path: '/etc/docker', user: 'root', group: 'root' }
        - { path: '/APP' }
        - { path: '/APP/configs' }
        - { path: '/APP/configs/filebeat' }
        - { path: '/APP/logs' }
        - { path: '/APP/logs/nginx' }
        - { path: '/APP/jenkins' }
        - { path: '/APP/jenkins/master' }
        - { path: '/APP/jenkins/node' }
        - { path: '/APP/portainer_data' }
 
    - name: Ставим нужные пакеты в систему
      zypper: 
        name: '{{ item }}'
      with_items:
        - docker
        - mc
        # Java необходима для запуска Jenkins-слейва
        - java-1_8_0-openjdk-headless
        
 
    # Помним о необходимости настроить подсети для Докера
    - name: Копируем конфиг-файл с "правильными сетями"
      template:
        src: daemon.json
        dest: /etc/docker/daemon.json
        owner: root
        group: root
        mode: 0644
 
    - name: Активируем и запускаем сервисы...
      systemd: 
        name: "{{ item }}"
        state: 'restarted'
        enabled: 'yes'
      with_items:
        - docker
        - sshd
 
    - name: Помещаем на целевой сервер docker-compose через скачивание на локальную
      get_url: 
        url: "https://github.com/docker/compose/releases/download/1.18.0/docker-compose-Linux-x86_64"
        dest: "/tmp/docker-compose"
      delegate_to: 127.0.0.1
  
    - copy: 
        src: "/tmp/docker-compose"
        dest: "/usr/local/bin/docker-compose"
        mode: "u=rwx,g=rx,o=rx"
 
 
    - name: Копируем конфиг-файл NGINX, конструируя его из шаблона
      template: 
        src: nginx.conf
        dest: /APP/configs/nginx.conf
        owner: dockeradm
        group: docker
        mode: '0644'
 
    - name: Копируем описание docker-compose - сервиса, конструируя его из шаблона
      template: 
        src: docker-compose.yml
        dest: /APP/docker-compose.yml
        owner: dockeradm
        group: docker
        mode: '0644'
  
    - name: Запуск docker-compose - сервис с Jenkins, Portainer и NGINX перед ними
      shell: docker-compose -f /APP/docker-compose.yml up -d --force-recreate
 
    - name: Ожидаем окончания инициализации Jenkins-а
      wait_for:
        path: '/APP/jenkins/master/secrets/initialAdminPassword'
 
    - name: Получаем значение временного пароля Jenkins
      fetch: 
        src: '/APP/jenkins/master/secrets/initialAdminPassword'
        dest: initialJenkinsAdminPassword.txt
        flat: yes

Часть 2. Сервисы проекта


2.1 CI-сервер и процесс


За процесс «непрерывной интеграции и деплоймента» в проекте отвечает хорошо многим известный сервер Jenkins CI.

Код Ansible playbook-и выше устроен так, что в конце его выполнения на сервере уже запущен свежеустановленный Jenkins (в Docker-контейнере), а его временный пароль сохранен на ЛОКАЛЬНОЙ машине в файле initialJenkinsAdminPassword.txt.

Поскольку всей команде хотелось максимально приблизиться к идеальному случаю Infrastructure as code (IaC), то в проекте таски были реализованы в виде декларативных и скриптованных пайплайнов Jenkins-а, когда задачи описываются на языке Groovy Script, а сам их код хранится рядом с исходниками проекте в системе контроля версий (git).

Пример пайплайна сборки backend-части приложения на Spring Boot показан ниже:

pipeline {
    agent {
        # указываем, что выполнять задачу хотим внутри 
        # Docker-контейнера на базе указанного образа:
        docker {
            image 'java:8-jdk'
        }
    }
    
    stages {
        stage('Стягиваем код из ГИТа') {
            steps {
                checkout scm
            }
        }
        stage('Собираем') {
            steps {
                sh 'chmod +x ./gradlew'
                sh './gradlew build -x test'
            }
 
        }
        stage('Тестируем') {
            steps {
                script {
                    sh './gradlew test'
                }
            }
        }
    }
}
 
#_ Этап сборки нового Docker-образа и его загрузки с систему Artifactory:
node {
    stage('Собираем образ') {
        docker.withRegistry("https://repo.artifactory.bank", "LoginToArtifactory") {
            def dkrImg = docker.build("repo.artifactory.bank/dev-backend:${env.BUILD_ID}")
            dkrImg.push()
            dkrImg.push('latest')
        }
	}
    stage('Заливаем его в Artifactory') {
        docker.withRegistry("https://repo.artifactory.bank", "LoginToArtifactory") {
            sh "docker service update --image repo.artifactory.bank/dev-backend:${env.BUILD_ID} SMB_dev-backend"
        }
    }
}

Отдельно хотелось бы отметить, что при сборке образов каждая версия получает свой тег (метку), что значительно облегчает процесс авторестарта приложения.

2.2 Portainer


Для облегчения взаимодействия всех членов команды с Docker в проекте нами был использован простой веб-интерфейс для него – Portainer. Данное приложение, так же, как и сам Docker, написано на языке Go, а поэтому отличается высокой производительностью при чрезвычайно легком развертывании.

Например, в простейшем случае следующая команда приведет к запуску Портейнера на порту 9000 хост-системы:

docker run -d 	-p 9000:9000 	-v /var/run/docker.sock:/var/run/docker.sock portainer/portainer

Однако, в текущем проекте было решено воспользоваться функциональностью средства «оркестрации» для одного хоста – Docker Compose.

2.3 Docker-контейнеры и сервисы


Все необходимые приложения и сервисы в данном проекте запускаются посредством простого файла docker-compose.yml.

Базовый набор «инфраструктурных» сервисов запускается посредством следующего описания:

version: '3.4'
 
services:
  # Непосредственно общающийся  с пользователями NGINX
  nginx:
    image: "nginx:1"
    container_name: fe-nginx
    restart: always
    volumes:
      - /APP/configs/nginx.conf:/etc/nginx/nginx.conf
      - /APP/logs/nginx:/var/log/nginx
      - /usr/share/zoneinfo/Europe/Moscow:/etc/localtime:ro
    networks:
      - int
    ports:
      - "80:80/tcp"
      - "8080:80/tcp"
 
  # Jenkins CI - сервер, который отвечает за CI/CD-процесс
  ci:
    image: "jenkins/jenkins:lts"
    container_name: ci-jenkins
    restart: always
    volumes:
        - /usr/share/zoneinfo/Europe/Moscow:/etc/localtime:ro
        - /APP/jenkins/master:/var/jenkins_home
    environment:
      JENKINS_OPTS: '--prefix=/jenkinsci'
      JAVA_OPTS: '-Xmx512m'
    networks:
      int:
        aliases:
          - srv-ci
 
  # Веб-интерфейс для Docker-а
  portainer:
    image: "portainer/portainer:latest"
    volumes:
      - type: bind
        source: /var/run/docker.sock
        target: /var/run/docker.sock
      - type: bind
        source: /APP/portainer_data
        target: /data
    networks:
      int:
        aliases:
          - srv-portainer
    command: -H 'unix:///var/run/docker.sock'
 
networks:
  int:
    external: true

2.4 Docker Swarm-кластер без кластера


Как можно видеть в файле docker-compose.yml выше, во-первых, отсутствуют упоминания бэкенд и фронтенд- частей приложения, а также присутствует ссылка на «внешнюю» (external: true) сеть по имени int. Внешними являются любые ресурсы (сети, тома и другие существующие сущности), не объявленные в одном файле.

Дело в том, что в проекте нам требовалась иметь возможность проводить рестарт «сервисов» при обновлении версии образа в Docker-репозитории Artifactory, а подобные функции присутствуют в сервисах Docker Swarm (встроенная в Docker multi-master (система оркестрации Docker-контейнеров) что называется «из коробки». Данная возможность реализуется через возможность изменить требуемый образ у запущенного сервиса, и при наличии новой версии образа в репозитории перезапуск произойдет автоматически. В случае же, если версия не изменилась – сервисный контейнер продолжает штатно выполняться.

Что касается сети, запуская приложение в виде сервиса Docker Swarm (yaml-описание представлено ниже), нам требовалось сохранить сетевую связность его компонентов и сервера NGINX, объявленного выше. Это было достигнуто через создание на сервере кластерой Overlay-сети, в которую включались и базовые сервисы, описанные выше, и непосредственно компоненты приложения:

docker network create -d overlay --subnet 10.1.2.254/24 --attachable int

(ключ --attachable является необходимым, т.к. без него базовые сервисы не имеют доступа к кластерной сети)

Описание компонентов приложения c двумя сервисами:

version: '3.2'
 
services:
  pre-live-backend:
    image:repo.artifactory.bank/dev-backend:latest
    deploy:
      mode: replicated
      replicas: 1
    networks:
      - int
  pre-live-front:
    image: repo.artifactory.bank/dev-front:latest
    deploy:
      mode: replicated
      replicas: 1
    networks:
      - int
networks:
  int:
    external: true

Заключение


Как было отмечено в начале, на старте проекта команде хотелось получить все преимущества DevOps-подхода, в частности организовать процесс непрерывной доставки кода от гит-репозитория до «боевого» сервера в виде запущенного на нем приложения. При этом на текущем этапе не хотелось полностью уходить от наработанных практик и перестраивать себя под жизнь в мире больших оркестраторов. Описанная архитектура системы, которая была продумана и реализована менее, чем за 2 недели (параллельно с другими проектами, над которыми трудились члены команды), в итоге позволила нам достичь желаемого. Мы полагаем, что данный материал должен быть интересным и полезным и другим командам, внедряющим DevOps-подходы в жизнь.

Комментарии (26)


  1. SicYar
    23.05.2018 16:04

    Спасибо за статью!
    было бы интересно узнать, как мониторите докер и сколько хостов с приложением работают одновременно?


    1. kruftik Автор
      23.05.2018 18:37
      +1

      в конкретно описываемом случае наиболее важной задачей была возможность ведения активной разработки и демонстрации актуального состояния системы бизнес-заказчикам при условии, что наша система взаимодействует с реальными базами и различными штуками в банке, поэтому развернуто приложение было на одном хосте, но в нескольких экземплярах (гит-мастер, тест, прелайв и тп)


    1. rjhdby
      23.05.2018 23:25

      Энтерпрайз+докер+мониторинг — это такое комбо, которое тянет как минимум на отдельную развернутую статью полную боли и страданий.


      1. SicYar
        24.05.2018 09:12

        Работаю непосредственно с таким комбо, поэтому интересуют подходы именно к мониторингу, может у кого то есть лучше практики, чем я использую :)


        1. kruftik Автор
          24.05.2018 18:16

          Моя и всех моих коллег светлая мечта постепенно прийти к решению, когда каждый сервис отдает некую health-check ручку, а система оркестрации за нее регулярно дергает.

          Со всеми «новыми» системами так и делаем с самого начала, а вот с существующими приходится всякие подпорки придумывать, и в них и тоска и боль и страдание, как верно подметил коллега выше.


  1. Yeah
    23.05.2018 17:34

    А как вы решаете вопрос с откатом по бизнес-требованиям?


    Предположим все тесты прошли, деплой прошел и все хорошо, но через пару часов выясняется, что что-то работает не так, как надо. Как в этом случае выполняется откат?


    1. vburmistrov
      23.05.2018 18:05
      +2

      Думаю, для отката должны быть припасены Git ветка productRelease с кодом ПО предыдущей версии, сборки обкатанных образов Docker в Artifactory, прочие необходимые настройки и артефакты и отдельный pipeline в Jenkins, который это развернет. В нашем проекте схема такая.

      kruftik, спасибо за статью.


      1. kruftik Автор
        23.05.2018 18:40
        +1

        Все почти так, за исключением того, что когда образ уже испечен, что там творится в гит-е уже не принципиально :)


    1. kruftik Автор
      23.05.2018 18:39
      +1

      В случае контейнерезированных приложений такие штуки решаются совершенно просто — каждая выкатываемая версия — это тег докер-образа с номером версии + тег latest. Если надо откатиться, мы просто запускаем контейнер из образа с тегом -1 от текущей и все.


  1. teamfighter
    23.05.2018 18:31
    +1

    Классный гайд, спасибо.
    Про прод не очень понял — на проде вы тоже в докере крутите приложения, или там всё-таки используется виртуализация\bare metal?
    Для своей компании никак не можем разрешить дилемму для нового проекта — т.к. у нас окружение приложения будет меняться крайне редко (в основном, security фиксы), есть ли смысл использовать докер вообще.


    1. kruftik Автор
      23.05.2018 18:43
      +1

      Да, все в докере, а сами контейнеры уже по разному бывает, где-то на голом железе, где-то в виртуалках.
      Мы уже какое-то количество лет весь новый софт и извне и разработанный внутри стараемся запускать в контейнерах, так всем проще. Исключения, конечно, случаются.


  1. nebiroz
    23.05.2018 18:44
    +1

    Спасибо за статью!
    В процессе чтения возник вопрос.
    А почему не было рассмотрено решение а-ля GIT + CI\CD + Task Manager в лице Gitlab?
    На своей работе я полностью ушел из Jenkins, который костыльным образом запускал проект, в сторону Gitlab и, в итоге, развернул систему, которая в режиме CI\CD делает сборку проекта сразу на 2 ОС (требования руководства) удаленно (то есть проект сразу разворачивается на нужных машинах): Windows и Linux.
    Любые изменения фиксируются в Task manager'е, чтобы можно было оперативно посмотреть изменения по задаче, которые были внесены.


    1. kruftik Автор
      23.05.2018 18:49
      +1

      Мы сейчас пилотируем разные интегрированные решения, с более дружелюбным интерфейсом, чем старик Jenkins, в том числе и гитлаб, но пока, к сожалению, золотого грааля отыскать не удалось :-(
      Огромный плюс Jenkins-а — наличие плагинов для любой кофеварки и достаточно посильный для многих Java-стэк разработчиков процесс написания собственных расширений.


  1. nebiroz
    23.05.2018 20:08
    +2

    Мне как раз для разных кофеварок и помогла специфика Gitlab в виде описания сценариев.
    В привязке к ОС и ее средствам (все нужные и удобные инструменты писал путем батников и shell скриптов) через gitlab runner.
    Вопрос был нацелен на попытку понять причину выбора между Jenkins и Gitlab в сторону Jenkins. При этом большинство авторов, которые выбрали Jenkins, описывают много проблем, связанных именно с Jenkins.
    Большинство тех проблем, которые они встречают, я не встретил в Gitlab.
    Поэтому было интересно узнать причину.
    И спасибо =)


    1. kruftik Автор
      23.05.2018 20:16
      +1

      GitLab хорош и часто даже весьма прекрасен своей высокой степенью интеграции и функциональности во том числе и бесплатной редакции при условии, что все серые будни кода протекают внутри него (гит-репозиторий, код-ревью, ci/cd-сервер, etc). Если же какие-то части этой инфраструктуры живут отдельно, то в значительная часть функциональности в бесплатной редакции перестает быть доступной, а его платные редакции кажутся очень-очень платными.

      Тем не менее, никто еще не ставил точки в вопросе выбора решения для подобных задач и GitLab в них не последний кандидат.


  1. kruftik Автор
    24.05.2018 07:12

    Me1ram,

    юзаете классический jenkins или jenkins blue ocean.

    — в данном проекте используем обычный интерфейс

    Если я правильно понял у вас докер репозиторий внутрий Артифактори?

    да, все верно


  1. Me1ram
    24.05.2018 08:07

    Эндпоинты в nginx к nodejs фронтовым приложениям создаете руками?
    То есть запустили nodejs фронт, как nginx догадается что нужно проксировать по эндпоинту на нужный nodejs фронт?


    1. kruftik Автор
      24.05.2018 13:04

      В данном проекте фронт-сервисов мало, поэтому просто руками дописываем, а близком по духу проекте по соседству используется схема, в которой используется docker-gen и прочие подобные штуки, когда вешается слушатель на события докера о появлении или смерти сервисов с определенными метками, генерирующий конфиг nginx-а.


  1. mitro
    24.05.2018 12:23

    Еще полезная вещь — динамическое развертывание окружений из feature branches. То есть кто-то создал ветку, пушнул её в репозиторий и CI/CD автоматически развернул систему из этой ветки. Я сделал такое решение на основе GitLab Review Apps. Адресация происходит по поддомену с именем ветки. К примеру, если пушится ветка с названием new-cool-feature, приложение разворачивается по адресу new-cool-feature.example.com. Эта ссылка автоматически указывается в описании пул-реквеста. То есть, кроме ревью кода можно также производить ревью непосредственно работающего приложения. После удаления ветки окружение полностью удаляется.

    Технически реализовал с помощью GitLab CI, Kubernetes и Helm. В исходниках лежит helm chart для разворачивания всей системы. При пуше изменений в ветку создается k8s namespace, если он еще не был создан. И потом происходит установка/обновление helm release. При удалении ветки и namespace и release удаляются.

    В итоге команде стало очень удобно тестировать разрабатываемый функционал. Пишешь код, пушишь в ветку, ждешь 5 минут и можешь пользоваться приложением на окружении.


    1. kruftik Автор
      24.05.2018 13:00

      да, элегантная и красивая схема, тоже думаем прийти к ней в скором времени, смотрим на разные варианты, думаем. Единственное, что всегда нужно думать о количестве разрабатываемых параллельно фич, а то сервера(ов) для запуска всех фича-бранчей может и не хватить, особенно если приложения достаточно монолитные и массивные :)


      1. mitro
        24.05.2018 13:20

        Да, эта схема требует больше ресурсов. Каждый пуш в origin ведёт к запуску всего пайплайна, сборке образов, прогону тестов (если есть), обновлению приложения на окружении. Но железо/виртуалки сейчас недорогие, зачастую ради удобства процесса проще их докупить. Ну и схема с kubernetes хороша тем, что если веток станет слишком много, то просто добавляем узлов в кластер и всё, ничего больше донастраивать не надо. Kubernetes сам позаботиться от том, чтобы распределить контейнеры по узлам.


  1. kudryavy
    24.05.2018 20:13
    +1

    Простите, а как связаны бизнес и снижение Т2М с развертыванием на тестовую среду?
    Бизнесу пока от вашего CI ни жарко, ни холодно. Продукт еще до пользователя надо довести, не только до разработчика (и возможно до тестировщика)


    1. kruftik Автор
      24.05.2018 20:19

      Совершенно верное замечание. В ответ на которое хотелось бы подчеркнуть, что процесс, при котором каждое изменение требует ручной выкатки на тестовую (ые) среду или хотя бы какого-бы то ни было участия гуманоидов, совершенно точно не сокращает пресловутый т2м, а скорее
      всего даже вовсе действует обратным образом.

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


      1. kudryavy
        24.05.2018 20:32
        +1

        :) Тогда зачем вы этот самый бизнес упомянули в самом начале? Потому что это начало пути к снижению т2м и бизнес смотрит на демонстрацию возможностей продукта на тестовом окружении?


        1. kruftik Автор
          24.05.2018 20:41

          Нет, все дело в том, бывают не только внешние юзеры, но и внутренние и стараться понижать т2м для последних тоже необходимо, поскольку их customer satisfaction, хотя и не напрямую, но оказывает сильное влияние на бизнес-показали компании.

          Действительно, наверное, стоило явно отметить, что под словом «бизнес» подразумевались любые подразделения, которые могут быть заказчиком продукта, в том числе и предназначенного для внутреннего пользователя, а не только явно приносящих прибыль непосредственно.


          1. kudryavy
            24.05.2018 22:33

            Понятно. Спасибо за разъяснения
            Я просто после фразы про бизнес почему-то ждал, что дело дойдет до промышленной эксплуатации. Ну или хотя бы внутренней промышленной эксплуатации.
            Для меня тестовая среда — это песочница для демонстрации или тестирования с тестовыми данными. А внутренние пользователи сидят на своем внутреннем проме. Или вы накатываете на внутренний пром?