Задумался как-то раз я об автоматизации развертывания своего проекта. gitlab.com любезно предоставляет для этого все инструменты, и я конечно решил воспользоваться, разобравшись и написав небольшой сценарий деплоя. В статье я делюсь своим опытом с сообществом.

TL;DR


  1. Настроить VPS: отключить root, вход по паролю, поставить dockerd, настроить ufw
  2. Сгенерировать сертификаты для сервера и клиента docs.docker.com/engine/security/https/#create-a-ca-server-and-client-keys-with-openssl Включить управление dockerd через tcp сокет: убрать опцию -H fd:// из конфига докера.
  3. Прописать пути до сертификатов в docker.json
  4. Прописать в переменные gitlab в настройках CI/CD с содержимым сертификатов. Написать скрипт .gitlab-ci.yml для деплоя.

Все примеры я буду показывать на дистрибутиве Debian.

Первоначальная настройка VPS


Вот вы купили инстанс например на DO, первое, что необходимо предпринять, это защитить ваш сервер от агрессивного внешнего мира. Я не буду ничего доказывать и утверждать, просто покажу лог /var/log/messages своего виртуального сервера:

Скриншот
image

Во-первых установим файервол ufw:

apt-get update && apt-get install ufw

Включим политику по-умолчанию: блокируем все входящие соединения, разрешаем все исходящие соединения:

ufw default deny incoming
ufw default allow outgoing

Важно: не забудем разрешить соединение по ssh:

ufw allow OpenSSH

Общий синтаксис такой: Разрешить соединение по порту: ufw allow 12345, где 12345 — номер порта или же название сервиса. Запретить: ufw deny 12345

Включаем файервол:

ufw enable

Выходим из сессии и снова логинимся по ssh.

Добавьте пользователя, назначьте ему пароль и добавьте его в группу sudo.

apt-get install sudo
adduser scoty
usermod -aG sudo scoty

Далее по плану следует отключить вход по паролю. для этого скопируйте ваш ssh-ключ на сервер:

ssh-copy-id root@10.101.10.28

ip сервера должен быть указан ваш. Попробуйте теперь залогиниться под созданным ранее пользователем, пароль вводить больше не надо. Далее в настройках конфигурации меняем следующее:

sudo nano /etc/ssh/sshd_config

отключаем вход по паролю:

PasswordAuthentication no

Перезапускаем демон sshd:

sudo systemctl reload sshd

Теперь если вы или кто-то другой попробует войти через пользователя root, у него ничего не получится.

Далее ставим dockerd, тут процесс уже не буду описывать, так как все может быть уже изменено, сходите по ссылке на официальный сайт и пройдите этапы установки docker на вашу виртуалку: https://docs.docker.com/install/linux/docker-ce/debian/

Генерация сертификатов


Чтобы управлять демоном докера удаленно требуется шифрованное TLS соединение. Для этого необходимо иметь сертификат и ключ, которые надо сгенерировать и перенести на удаленную вашу машину. Следуйте шагам, заданным в инструкции на официальном сайте docker: https://docs.docker.com/engine/security/https/#create-a-ca-server-and-client-keys-with-openssl Все сгенерированные *.pem файлы для сервера, а именно ca.pem, server.pem, key.pem надо поместить в директорию /etc/docker на сервере.

Настройка dockerd


В сценарии запуска демона docker убираем опцию -H df://, эта опция отвечает, на каком хосте можно управлять демоном докера.

# At /lib/systemd/system/docker.service
[Service]
Type=notify
ExecStart=/usr/bin/dockerd

Далее следует создать файл настроек, если его еще нет и прописать опции:

/etc/docker/daemon.json
{
  "hosts": [
    "unix:///var/run/docker.sock",
    "tcp://0.0.0.0:2376"
  ],
  "labels": [
    "is-our-remote-engine=true"
  ],
  "tls": true,
  "tlscacert": "/etc/docker/ca.pem",
  "tlscert": "/etc/docker/server.pem",
  "tlskey": "/etc/docker/key.pem",
  "tlsverify": true
}


Разрешим подключения по порту 2376:

sudo ufw allow 2376

Перезапустим dockerd с новыми настройками:

sudo systemctl daemon-reload && sudo systemctl restart docker

Проверим:

sudo systemctl status docker

Если все «зеленое», то считаем, что на сервере мы успешно настроили docker.

Настройка continuous deleivery на gitlab


Для того чтобы воркер гиталаба смог выполнять команды на удаленном хосте докера необхоимо определиться, как и где хранить сертификаты и ключ для шифрованного соединения с dockerd. Я решил данную проблему просто прописав в переменные в настройках gitlbab:

Заголовок спойлера
image

Просто выводите содержимое сертификатов и ключа через cat: cat ca.pem. Копируете и вставляете в значение переменных.

Пропишем сценарий для деплоя через гитлаб. Использовать будет docker-in-docker (dind) образ.

.gitlab-ci.yml
image:
  name: docker/compose:1.23.2
  # перепишем entrypoint , чтобы работало в dind
  entrypoint: ["/bin/sh", "-c"]

variables:
  DOCKER_HOST: tcp://docker:2375/
  DOCKER_DRIVER: overlay2

services:
  - docker:dind

stages:
  - deploy

deploy:
  stage: deploy
  script:
    - bin/deploy.sh # скрипт деплоя тут


Содержимое скрипта деплоя с комментариями:

bin/deploy.sh
#!/usr/bin/env sh
# Падаем сразу, если возникли какие-то ошибки
set -e
# Выводим, то , что делаем
set -v

# 
DOCKER_COMPOSE_FILE=docker-compose.yml
# Куда деплоим
DEPLOY_HOST=185.241.52.28
# Путь для сертификатов клиента, то есть в нашем случае - gitlab-воркера
DOCKER_CERT_PATH=/root/.docker

# проверим, что в контейнере все имеется
docker info
docker-compose version

# создаем путь (сейчас работаем в клиенте - воркере gitlab'а)
mkdir $DOCKER_CERT_PATH
# изымаем содержимое переменных, при этом удаляем лишние символы добавленные при сохранении переменных.
echo "$CA_PEM" | tr -d '\r' > $DOCKER_CERT_PATH/ca.pem
echo "$CERT_PEM" | tr -d '\r' > $DOCKER_CERT_PATH/cert.pem
echo "$KEY_PEM" | tr -d '\r' > $DOCKER_CERT_PATH/key.pem
# на всякий случай даем только читать
chmod 400 $DOCKER_CERT_PATH/ca.pem
chmod 400 $DOCKER_CERT_PATH/cert.pem
chmod 400 $DOCKER_CERT_PATH/key.pem

# далее начинаем уже работать с удаленным docker-демоном. Собственно, сам деплой
export DOCKER_TLS_VERIFY=1
export DOCKER_HOST=tcp://$DEPLOY_HOST:2376

# проверим, что коннектится все успешно
docker-compose   -f $DOCKER_COMPOSE_FILE   ps

# логинимся в docker-регистри, тут можете указать свой "местный" регистри
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD

docker-compose   -f $DOCKER_COMPOSE_FILE   pull app
# поднимаем приложение
docker-compose   -f $DOCKER_COMPOSE_FILE   up -d app


Основная проблема была в том, чтобы «вытащить» из переменных gitlab CI/CD содержимое сертификатов в нормальном виде. Я не мог понять, почему не работало соединение с удаленным хостом. На хосте посмотрел журнал sudo journalctl -u docker, там ошибка при рукопожатии. Решил глянуть, что вообще хранится в переменных, для этого можно посмотреть так cat -A $DOCKER_CERT_PATH/key.pem. Ошибку поборол, добавив удаление символа каретки tr -d '\r'.

Далее в сценарий можно добавить пост-релизные таски на свое усмотрение. Ознакомиться с рабочей версией можете в моем репозитории https://gitlab.com/isqad/gitlab-ci-cd

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


  1. Dal
    29.04.2019 12:09

    Просто выводите содержимое сертификатов и ключа через cat: cat ca.pem. Копируете и вставляете в значение переменных.

    Основная проблема была в том, чтобы «вытащить» из переменных gitlab CI/CD содержимое сертификатов в нормальном виде.

    Ошибку поборол, добавив удаление символа каретки tr -d '\r'.

    Просто копировали с этим символом, вот и пришлось его удалять.
    Нашел свой старый проект, там вот так
    - echo "$DOCKER_TLSCA_DEV" > ~/.docker/ca.pem



    1. DrAndyHunter Автор
      29.04.2019 12:13

      Да, вы правы скорее всего.


  1. Graphite
    29.04.2019 17:56
    +1

    У нас похожая схема, только deploy.sh подключается через SSH с ключом и работает через локальный сокет на сервере. По большому счету разницы никакой — тот у кого есть доступ к docker сокету все равно фактически root, зато у нас всего одна переменная SSH_PRIVATE_KEY, вместо пяти. Другой условный плюс — zero day в авторизации в докер сокете нас не затронет, а от zero day в SSH все равно никуда не деться.


    Docker вместе с ufw / iptables нужно использовать аккуратно, т.к. он модфицирует iptables правила напрямую, что может привести к непредсказуемым результатам. В вашем случае все вроде бы нормально, но если docker-compose будет посложнее, то может оказаться, что нечто доступно извне, хотя и не должно. Например, контейнер с nginx плюс контейнер с MongoDB общающиеся по внутренней сети при неосторожном движении могут легко оставить MongoDB открытой всему миру без авторизации.


  1. nightvich
    30.04.2019 12:31

    Настройка continuous deleivery на gitlab

    Это что-то типо непрерывной задержки?