Перейти к содержанию

▍CI/CD в GitLab

Настройка CI/CD в GitLab для автоматической сборки проекта сайта на MkDocs

В нашем случае будет использоваться связка docker контейнеров:

  • Traefik
  • Gitlab
  • Gitlab-runner
  • Nginx

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

sudo mkdir -p /srv/docker/{conf,data/www}

Описывать параметры контейнеров будем в файле /srv/docker/docker-compose.yml.

Настройка Traefik

# Пропишем версию
version: '3.3'

# Перечислим сервисы
services:
   traefik:
        image: traefik:latest
        container_name: traefik
        restart: always
        security_opt:
          - no-new-privileges:true
        ports:
          - 192.168.0.111:80:80
          - 192.168.0.111:443:443/tcp
          - 192.168.0.111:443:443/udp
        environment:
          - SELECTEL_API_TOKEN=345645456wefgsdg76dg8dg7d78g67dfg
        volumes:
          - $DIR_CONF/traefik/traefik.yml:/traefik.yml:ro
          - $DIR_CONF/traefik/acme.json:/acme.json
          - $DIR_CONF/traefik/config.yml:/config.yml:ro
          - /var/run/docker.sock:/var/run/docker.sock:ro
          - /etc/localtime:/etc/localtime:ro
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.traefik.entrypoints=http"
          - "traefik.http.routers.traefik.rule=Host(`traefik.example.ru`)"
          - "traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https"
          - "traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto=https"
          - "traefik.http.middlewares.traefik-auth.basicauth.users=username:$$apr1$$QDKFN7LI$$iz.jL7bGBJCEygQDKQdUC0"
          - "traefik.http.routers.traefik.middlewares=traefik-https-redirect"
          - "traefik.http.routers.traefik-secure.entrypoints=https"
          - "traefik.http.routers.traefik-secure.rule=Host(`traefik.example.ru`)"
          - "traefik.http.routers.traefik-secure.middlewares=traefik-auth"
          - "traefik.http.routers.traefik-secure.tls=true"
          - "traefik.http.routers.traefik-secure.tls.certresolver=cloudflare"
          - "traefik.http.routers.traefik-secure.tls.domains[0].main=example.ru"
          - "traefik.http.routers.traefik-secure.tls.domains[0].sans=*.example.ru"
          - "traefik.http.middlewares.test-ipwhitelist.ipwhitelist.ipstrategy.excludedips=172.18.0.0/24"
          - "traefik.http.middlewares.WhitelistHome.ipwhitelist.sourcerange=172.18.0.0/24, 192.168.0.0/16"
          - "traefik.http.routers.traefik-secure.service=api@internal"

В моём случае настройка DNS записей производится через API Selectel. Чтобы задать параметр SELECTEL_API_TOKEN нужно перейти на страницу Ключи API, нажав на Новый ключ.

Для дополнительной защиты админки используется парольная авторизация, это параметр:

"traefik.http.middlewares.traefik-auth.basicauth.users=username:$$apr1$$QDKFN7LI$$iz.jL7bGBJCEygQDKQdUC0"

Где в качестве логина испльзуется username, а пароль: test12345 Для генерации этой пары воспользуйтесь командой:

echo $(htpasswd -nb username test12345) | sed -e s/\\$/\\$\\$/g

Также настроен белый список для доступа к админке, это параметр:

"traefik.http.middlewares.WhitelistHome.ipwhitelist.sourcerange=172.18.0.0/24, 192.168.0.0/16"

Для поддержки HTTP/3 необходимо указать как TCP так и UDP для 443 порта, также не забыть открыть их на фаерволе.

          - 192.168.0.111:443:443/tcp
          - 192.168.0.111:443:443/udp

/traefik/config.yml
http:
  routers:
    kibana:
      entryPoints:
        - "https"
      rule: "Host(`kibana.example.ru`)"
      middlewares:
        - WhitelistHome
      tls: {}
      service: kibana

  services:
    kibana:
      loadBalancer:
        servers:
          - url: "http://192.168.0.10:5601"
        passHostHeader: true

  middlewares:

    default-headers:
      headers:
        frameDeny: true
        sslRedirect: true
        browserXssFilter: true
        contentTypeNosniff: true
        forceSTSHeader: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 31536000
        customFrameOptionsValue: SAMEORIGIN
        customRequestHeaders:
          X-Forwarded-Proto: https
        customResponseHeaders:
          server: Blackbox
          x-powered-by: Blackbox
        referrerpolicy: same-origin
        #contentSecurityPolicy: script-src 'self'
        permissionsPolicy: geolocation=(self), microphone=(), camera=()

    WhitelistHome:
      ipWhiteList:
        sourceRange:
        - "10.0.0.0/8"
        - "192.168.0.0/16"
        - "172.16.0.0/12"

    secured:
      chain:
        middlewares:
        - default-whitelist
        - default-headers

tls:
  options:
    default:
      cipherSuites:
        - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384   # TLS 1.2
        - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305    # TLS 1.2
        - TLS_AES_256_GCM_SHA384                  # TLS 1.3
        - TLS_CHACHA20_POLY1305_SHA256            # TLS 1.3
      curvePreferences:
        - CurveP521
        - CurveP384
      minVersion: VersionTLS12
      sniStrict: true

    mintls13:
      minVersion: VersionTLS13
/traefik/traefik.yml
api:
  dashboard: true
  debug: true
experimental:
  http3: true
entryPoints:
  http:
    address: ":80"
    http:
      redirections:
        entrypoint:
          to: https
          scheme: https
          permanent: true
  https:
    address: ":443"
    forwardedHeaders:
      trustedIPs:
        - "127.0.0.1/32"
        - "10.0.0.0/8"
        - "192.168.0.0/16"
        - "172.16.0.0/12"
    http3: {}
serversTransport:
  insecureSkipVerify: true
providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
  file:
    filename: /config.yml
    watch: true
certificatesResolvers:
  selectel:
    acme:
      email: [email protected]
      storage: acme.json
      dnsChallenge:
        provider: selectel
        delayBeforeCheck: 10
        resolvers:
          - "188.68.203.5:53"
          - "77.223.114.10:53"
          - "188.68.203.10:53"
          - "77.223.114.5:53"
          - "1.1.1.1:53"
          - "8.8.8.8:53"

В файле /srv/docker/.env пропишем пути:

DIR_CONF=/srv/docker/conf
DIR_DATA=/srv/docker/data

Теперь можно и запустить контейнер:

cd /srv/docker
docker-compose up -d

Настройка Gitlab

version: '3.3'

services:
   gitlab:
        image: gitlab/gitlab-ce:latest
        container_name: gitlab
        restart: always
        environment:
          GITLAB_OMNIBUS_CONFIG: |
            external_url 'https://gitlab.example.ru'
            nginx['listen_port'] = 80
            nginx['listen_https'] = false
            nginx['proxy_set_headers'] = {
            "X-Forwarded-Proto" => "https",
            "X-Forwarded-Ssl" => "on"
            }
            gitlab_rails['smtp_enable'] = true
            gitlab_rails['smtp_address'] = "smtp.mail.ru"
            gitlab_rails['smtp_port'] = 465
            gitlab_rails['smtp_user_name'] = "maillogin"
            gitlab_rails['smtp_password'] = "mailpassword"
            gitlab_rails['smtp_domain'] = "mail.ru"
            gitlab_rails['smtp_authentication'] = "login"
            gitlab_rails['smtp_enable_starttls_auto'] = false
            gitlab_rails['smtp_tls'] = true
            gitlab_rails['smtp_openssl_verify_mode'] = 'peer'
            gitlab_rails['gitlab_email_from'] = 'maillogin'
            gitlab_rails['gitlab_email_reply_to'] = 'maillogin'
        shm_size: '256m'
        volumes:
          - $DIR_CONF/gitlab:/etc/gitlab
          - $DIR_DATA/gitlab:/var/opt/gitlab
          - /etc/localtime:/etc/localtime:ro
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.gitlab.entrypoints=http"
          - "traefik.http.routers.gitlab.rule=Host(`gitlab.example.ru`)"
          - "traefik.http.middlewares.gitlab-https-redirect.redirectscheme.scheme=https"
          - "traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto=https"
          - "traefik.http.routers.gitlab-secure.middlewares=WhitelistHome"
          - "traefik.http.routers.gitlab.middlewares=gitlab-https-redirect"
          - "traefik.http.routers.gitlab-secure.entrypoints=https"
          - "traefik.http.routers.gitlab-secure.rule=Host(`gitlab.example.ru`)"
          - "traefik.http.routers.gitlab-secure.tls=true"
          - "traefik.http.routers.gitlab-secure.service=gitlab"
          - "traefik.http.services.gitlab.loadbalancer.server.port=80"

Если вы используете свой домен подключеyный к VK Work Space, то maillogin указываете целиком - maillogin@example.ru при этом smtp_domain остается mail.ru.

Снова выполняем docker-compose up -d, ждём... долго...

Проверить статус загрузки можно командой:

docker logs gitlab -f
Периодически пытаемя заходим на gitlab.example.ru. Если удалось, то достаём пароль от панели управления:

$ docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password

Соотвественно для авторизации используем логин root и пароль из вывода сверху.

Настройка CI/CD в Gitlab

После авторизации в настройках профиля можно будет изменить язык на русский. Создаем новый проект и переходим слева в меню в "Настройки" -> "CI/CD" -> разворачиваем "Runner'ы". Для настройки самого runner нам понадобятся данные URL и токен регистрации.

Панель управления Gitlab, настройка CI/CD

Добавляем в /srv/docker/docker-compose.yml описание Gitlab-runner:

   gitlab-runner:
        image: 'gitlab/gitlab-runner:latest'
        container_name: gitlab-runner
        restart: always
        links:
          - gitlab
        volumes:
          - $DIR_CONF/gitlab-runner:/etc/gitlab-runner
          - /var/run/docker.sock:/var/run/docker.sock
          - /etc/localtime:/etc/localtime:ro

Выполняем docker-compose up -d и запускаем процесс регистрации нашего ранера: Добавляем в /srv/docker/docker-compose.yml описание Gitlab-runner:

docker exec -ti gitlab-runner gitlab-runner register
[user@host:/srv/docker]$ docker exec -ti gitlab-runner gitlab-runner register
Runtime platform                                    arch=amd64 os=linux pid=29 revision=c6bb62f6 version=14.10.0
Running in system-mode.                            

Enter the GitLab instance URL (for example, https://gitlab.com/):
https://gitlab.example.ru
Enter the registration token:
XMvANz3QF6fFgFsivdsocNsSAp7M1s
Enter a description for the runner:
[ffffffffffff]: blog
Enter tags for the runner (comma-separated):
mkdocs* 
Enter optional maintenance note for the runner:

Registering runner... succeeded                     
Enter an executor: docker, docker-ssh, ssh, docker-ssh+machine, kubernetes, custom, parallels, shell, virtualbox, docker+machine:
docker
Enter the default Docker image (for example, ruby:2.7):
squidfunk/mkdocs-material
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded! 

URL и токен нужно указать такие же как и на странице настроек в панели управления Gitlab. Пункты "Enter an executor" и "Enter the default Docker image" нужно оставить как указано.

Регистрация Gitlab-runner

Настройка Nginx

   nginx:
        image: nginx:latest
        container_name: nginx
        restart: always
        volumes:
          - $DIR_DATA/www:/www
          - $DIR_CONF/nginx:/etc/nginx
          - /etc/localtime:/etc/localtime:ro
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.nginx.entrypoints=http"
          - "traefik.http.routers.nginx.rule=Host(`example.ru`, `www.example.ru`)"
          - "traefik.http.middlewares.nginx-https-redirect.redirectscheme.scheme=https"
          - "traefik.http.routers.nginx.middlewares=nginx-https-redirect"
          - "traefik.http.routers.nginx-secure.entrypoints=https"
          - "traefik.http.routers.nginx-secure.rule=Host(`example.ru`, `www.example.ru`)"
          - "traefik.http.routers.nginx-secure.tls=true"
          - "traefik.http.routers.nginx-secure.tls.certresolver=http"
          - "traefik.http.routers.nginx-secure.service=nginx"
          - "traefik.http.services.nginx.loadbalancer.server.port=80"

Выполняем docker-compose up -d, и создаем конфиг нового сайта:

sudo nano /srv/docker/conf/nginx/sites-enabled
server {
        listen 80;
        server_name example.ru www.example.ru;

        root /www/example.ru/site;

location / {
        index index.html;
}

location ~* ^.+\.(jpg|jpeg|gif|png|ico|css|pdf|ppt|txt|bmp|rtf|js|woff)$ {
        expires 30d;
}

}

Проверяем корректность синтаксиса в конфигах nginx:

docker exec -ti nginx nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Если всё ок, то делаем reload сервера:

docker kill -s HUP nginx

Тепеь создадим пустышку сайта, для этого перейдём в директорию /srv/docker/data/www/example.ru и выполним команду:

docker run --rm -it -v ${PWD}:/docs squidfunk/mkdocs-material new .

В результате у нас появятся два файла:

.
├─ docs/
│  └─ index.md
└─ mkdocs.yml

Файл mkdocs.yml — это файл конфигурации, используемый MkDocs. Файлы, находящиеся в папке docs (в частности, index.md) — это исходные материалы, из которых в дальнейшем будет сгенерирован наш сайт.

Генерируем сайт по существующим материалам:

docker run --rm -it -v ${PWD}:/docs squidfunk/mkdocs-material build

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

Настройка репозитория

Теперь настроим Gitlab CI и Gitlab Runner для генерации статического сайта. Для этого нам понадобиться добавить наш сайт в систему контроля версии и создать файл .gitlab-ci.yml, который будет автоматически собирать наш сайт после сохранения новых изменений.

  1. Перейдём в директорию /srv/docker/data/www/example.ru
  2. Создадим файл .gitignore и внесём в него 1 строку (команду игнорировать папку site); для этого в терминале введём команду
    echo '[Ss]ite/' > .gitignore
    
  3. Создадим репозиторий командой
    git init
    
  4. Создадим первый коммит, состоящий из всех созданных нами файлов
    git add -A && git commit -m 'Новый сайт'
    
    Снова вернёмся в панель управления Gitlab, где нужно будет скопировать путь до нашего проекта:

Далее воспользуемся этой ссылкой для сохранения созданного репозитория в созданный проект gitlab:

git remote add origin <ссылка на проект gitlab>
git push -u origin master

Теперь, когда репозиторий создан и связан с Gitlab, создадим в папке с исходными материалами файл .gitlab-ci.yml:

stages:
  - build
  - deploy

build_docs_job:
  stage: build
  tags: [mkdocs]
  only:
    - /^master$/
    - merge_requests
  image:
    name: squidfunk/mkdocs-material
    entrypoint: [""]
  script:
    - 'mkdocs build --site-dir site'
  artifacts:
    name: "site_$($CI_PIPELINE_IID)"
    paths:
      - site

deploy:
  stage: deploy
  tags: [mkdocs]
  image:
    name: squidfunk/mkdocs-material
    entrypoint: [""]
  dependencies:
    - build_docs_job
  only:
    - /^master$/
  before_script:
    - (if [ -d "site" ]; then echo ok; else exit "no build folder, try to run pipeline again"; fi);
  script:
    - cp -r site/* /site/

Чтобы сработал deploy и были скопированы файлы при работе команды:

 cp -r site/* /site/
нужно прокинуть папку /srv/data/www/example.ru/site внуть docker контейнера squidfunk/mkdocs-material, который будет запускаться ранером в процессе сборки. Для этого отредактируем файл:

sudo nano /srv/docker/conf/gitlab-runner/config.toml
где параметр:
    volumes = ["/cache"]

заменить на:
    volumes = ["/cache", "/srv/docker/data/www/example.ru/site:/site"]

Этим файлом мы указали Gitlab CI собирать сайт каждый раз, когда появляются новые коммиты в ветке master или в процессе создания или обновления merge request-ов.

Настройка почтовых уведомлений через SMTP

Для проверки корректности работы SMTP зайдём в консоль контейнера:

docker exec -it gitlab bash
Далее заходим в консоль гита:
gitlab-rails console -e production

и выполняем отправку тестового письма:

Notify.test_email('[email protected]', 'zagolovok_gitlab email', 'Test_mail').deliver_now

GitLab SMTP Check

Ошибки и их решения

Развернуть
$ git commit -m 'Новый сайт'
*** Пожалуйста, скажите мне кто вы есть.

Запустите

  git config --global user.email "[email protected]"
  git config --global user.name "Ваше Имя"

для указания идентификационных данных аккаунта по умолчанию.
Пропустите параметр --global для указания данных только для этого репозитория.
$ git clone https://gitlab.example.ru/user/blog.git
Клонирование в «blog»…
fatal: unable to access 'https://gitlab.example.ru/user/blog.git/': server certificate verification failed. CAfile: none CRLfile: none
Решение:
GIT_SSL_NO_VERIFY=true git clone https://gitlab.example.ru/user/blog.git

Полезные ссылки

Полный список переменных: https://docs.gitlab.com/ee/ci/variables/predefined_variables.html

К началу