├── production
├── traefik
│ ├── logs
│ │ └── .gitkeep
│ ├── letsencrypt
│ │ └── .gitkeep
│ ├── traefik.yml
│ └── dynamic-conf.yml
├── demo-prj
│ ├── db
│ │ └── data
│ │ │ └── .gitkeep
│ └── backend
│ │ └── media
│ │ └── .gitkeep
├── gitlab-runner
│ └── config
│ │ └── .gitkeep
└── letsencrypt-dns
│ └── domains.conf
├── services
└── gitlab
│ ├── config
│ └── .gitkeep
│ ├── data
│ └── .gitkeep
│ └── logs
│ └── .gitkeep
├── staging
└── gitlab-runner
│ ├── cache
│ └── .gitkeep
│ ├── config
│ └── .gitkeep
│ └── jobs
│ └── .gitkeep
├── .dockerignore
├── img
└── scheme.png
├── .gitignore
├── docker-compose.staging.yml
├── demo_cicd.iml
├── docker-compose.prod.yml
├── .gitlab-ci.yml
├── docker-compose.services.yml
└── README.md
/production/traefik/logs/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/services/gitlab/config/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/services/gitlab/data/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/services/gitlab/logs/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/production/demo-prj/db/data/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/staging/gitlab-runner/cache/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/staging/gitlab-runner/config/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/staging/gitlab-runner/jobs/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/production/demo-prj/backend/media/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/production/gitlab-runner/config/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/production/traefik/letsencrypt/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/production/letsencrypt-dns/domains.conf:
--------------------------------------------------------------------------------
1 | *.dev.company.ru
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | .git
2 | .idea
3 | .gitlab-ci.yml
4 | *.iml
5 | *.md
6 | /certs
--------------------------------------------------------------------------------
/img/scheme.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Akkarine/demo_cicd/HEAD/img/scheme.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | !.idea/modules.xml
3 | !.idea/vcs.xml
4 | !.idea/deployment.xml
5 | certs/
6 | !certs/production-docker/.gitkeep
7 | !certs/services-docker/.gitkeep
8 | !certs/staging-docker/.gitkeep
9 | production/demo-prj/db/backupsrvkey
10 |
--------------------------------------------------------------------------------
/docker-compose.staging.yml:
--------------------------------------------------------------------------------
1 | version: '3.7'
2 |
3 | services:
4 |
5 | gitlab-runner:
6 | image: gitlab/gitlab-runner:latest
7 | restart: always
8 | container_name: gitlab-runner
9 | volumes:
10 | - /srv/gitlab-runner/config:/etc/gitlab-runner
11 | - /var/run/docker.sock:/var/run/docker.sock
--------------------------------------------------------------------------------
/demo_cicd.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/production/traefik/traefik.yml:
--------------------------------------------------------------------------------
1 | global:
2 | sendAnonymousUsage: true
3 |
4 | entryPoints:
5 | ssh-gitlab:
6 | address: ":1023"
7 | web:
8 | address: ":80"
9 | web-secure:
10 | address: ":443"
11 | traefik:
12 | address: ":8080"
13 |
14 | providers:
15 | file:
16 | filename: "/etc/traefik/dynamic-conf.yml"
17 | docker:
18 | # https://docs.traefik.io/providers/docker/#endpoint
19 | # https://docs.docker.com/engine/security/https/
20 | endpoint: "tcp://192.168.88.3:2376"
21 | exposedByDefault: false
22 | useBindPortIP: true
23 | tls:
24 | ca: /certs/staging/ca.pem
25 | cert: /certs/staging/cert.pem
26 | key: /certs/staging/key.pem
27 |
28 | api:
29 | dashboard: true
30 | # insecure: true
31 |
32 | log:
33 | filepath: "/logs/traefik.log"
34 | format: common
35 | level: INFO
36 |
37 | accessLog:
38 | filepath: "/logs/access.log"
39 | # bufferingSize: 100
40 | filters:
41 | statusCodes:
42 | - "400-526"
43 |
44 | certificatesResolvers:
45 | letsencrypt:
46 | acme:
47 | email: "mail@company.com"
48 | storage: "/letsencrypt/acme.json"
49 | # закомменитровать caServer, если тестовый challenge будет успешен
50 | caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
51 | tlsChallenge: {}
52 |
--------------------------------------------------------------------------------
/docker-compose.prod.yml:
--------------------------------------------------------------------------------
1 | version: '3.7'
2 |
3 | services:
4 |
5 | gitlab-runner:
6 | image: gitlab/gitlab-runner:latest
7 | restart: always
8 | container_name: gitlab-runner
9 | volumes:
10 | - /srv/gitlab-runner/config:/etc/gitlab-runner
11 | - /var/run/docker.sock:/var/run/docker.sock
12 |
13 | letsencrypt-dns:
14 | restart: always
15 | image: adferrand/letsencrypt-dns:latest
16 | container_name: "letsencrypt-dns"
17 | volumes:
18 | - /srv/letsencrypt-dns:/etc/letsencrypt
19 | - /usr/share/zoneinfo:/usr/share/zoneinfo:ro
20 | environment:
21 | - "LETSENCRYPT_USER_MAIL=$LETSENCRYPT_EMAIL"
22 | - "LEXICON_PROVIDER=yandex"
23 | - "LEXICON_PROVIDER_OPTIONS=--auth-token=$DNS_PROVIDER_API_TOKEN"
24 | - "LEXICON_SLEEP_TIME=300"
25 | - "TZ=Europe/Volgograd"
26 |
27 | traefik:
28 | restart: always
29 | image: traefik:latest
30 | container_name: traefik
31 | ports:
32 | - 1023:1023
33 | - 80:80
34 | - 443:443
35 | - 8080:8080
36 | volumes:
37 | - /srv/traefik/traefik.yml:/etc/traefik/traefik.yml
38 | - /srv/traefik/dynamic-conf.yml:/etc/traefik/dynamic-conf.yml
39 | - /srv/traefik/letsencrypt:/letsencrypt
40 | - /srv/traefik/certs:/certs
41 | - /srv/traefik/logs:/logs
42 | - /usr/share/zoneinfo:/usr/share/zoneinfo:ro
43 | environment:
44 | - "TZ=Europe/Volgograd"
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | stages:
2 | - update-runners
3 | - deploy
4 | - update-sertificates
5 |
6 | variables:
7 | DOCKER_DRIVER: overlay2
8 | DOCKER_BUILDKIT: 1
9 |
10 | update-letsencrypt-dns:
11 | image: tmaier/docker-compose:latest
12 | stage: deploy
13 | script:
14 | # gitlab-runner хранит текущую git-версию файлов в своей папке работающего раннера джобы
15 | - cp -r $CI_PROJECT_DIR/production/letsencrypt-dns/domains.conf /srv/letsencrypt-dns/domains.conf
16 | - docker-compose -f docker-compose.prod.yml pull letsencrypt-dns
17 | - docker-compose -f docker-compose.prod.yml up -d letsencrypt-dns
18 | when: manual
19 | tags:
20 | - production
21 |
22 | update-traefik:
23 | image: tmaier/docker-compose:latest
24 | stage: deploy
25 | script:
26 | - cp -r $CI_PROJECT_DIR/production/traefik/dynamic-conf.yml /srv/traefik/dynamic-conf.yml
27 | - cp -r $CI_PROJECT_DIR/production/traefik/traefik.yml /srv/traefik/traefik.yml
28 | - docker-compose -f docker-compose.prod.yml -p prod pull traefik
29 | - docker-compose -f docker-compose.prod.yml -p prod up -d traefik
30 | when: manual
31 | tags:
32 | - production
33 |
34 | # Интересно, даст ли сам себя обновить, хотя команды раннер выполняет из другого контейнера.
35 | .update-runners: &update-runners
36 | image: tmaier/docker-compose:latest
37 | stage: update-runners
38 | when: manual
39 |
40 | update-production-runner:
41 | <<: *update-runners
42 | script:
43 | - docker-compose -f docker-compose.prod.yml pull gitlab-runner
44 | - docker-compose -f docker-compose.prod.yml -p prod up -d gitlab-runner
45 | tags:
46 | - production
47 |
48 | update-staging-runner:
49 | <<: *update-runners
50 | script:
51 | - docker-compose -f docker-compose.staging.yml pull gitlab-runner
52 | - docker-compose -f docker-compose.staging.yml -p staging up -d gitlab-runner
53 | tags:
54 | - staging
55 |
--------------------------------------------------------------------------------
/docker-compose.services.yml:
--------------------------------------------------------------------------------
1 | version: '3.7'
2 |
3 | services:
4 |
5 | gitlab:
6 | image: 'gitlab/gitlab-ce:latest'
7 | restart: always
8 | container_name: gitlab
9 | hostname: 'gitlab.company.ru'
10 | environment:
11 | # https://community.hetzner.com/tutorials/gitlab-server-with-docker#step-5-prepare-the-registry
12 | # https://www.davd.io/byecloud-gitlab-with-docker-and-traefik/
13 | # https://forum.gitlab.com/t/container-registry-behind-reverse-proxy/6848/2
14 | GITLAB_OMNIBUS_CONFIG: |
15 | grafana['enable'] = false
16 | external_url "https://gitlab.company.ru"
17 | nginx['proxy_set_headers'] = {
18 | # $$ - экранирование $ в .yml для передачи в конфигурацию gitlab.rb
19 | "Host" => "$$http_host",
20 | "X-Real-IP" => "$$remote_addr",
21 | "X-Forwarded-For" => "$$proxy_add_x_forwarded_for",
22 | "X-Forwarded-Proto" => "https",
23 | "X-Forwarded-Ssl" => "on"
24 | }
25 | nginx['listen_port'] = 80
26 | nginx['listen_https'] = false
27 | gitlab_rails['time_zone'] = 'Europe/Volgograd'
28 | gitlab_rails['trusted_proxies'] = ['192.168.88.1', '192.168.88.2']
29 | registry_external_url 'https://registry.company.ru'
30 | registry['enable'] =true
31 | registry_nginx['enable'] = true
32 | registry_nginx['proxy_set_headers'] = {
33 | "Host" => "$$http_host",
34 | "X-Real-IP" => "$$remote_addr",
35 | "X-Forwarded-For" => "$$proxy_add_x_forwarded_for",
36 | "X-Forwarded-Proto" => "https",
37 | "X-Forwarded-Ssl" => "on"
38 | }
39 | registry_nginx['listen_port'] = 5005
40 | registry_nginx['listen_https'] = false
41 | nginx['real_ip_trusted_addresses'] = ['192.168.88.1', '192.168.88.2']
42 | nginx['real_ip_header'] = 'X-Forwarded-For'
43 | nginx['real_ip_recursive'] = 'on'
44 | ports:
45 | - '80:80'
46 | - '1023:22'
47 | - '5000:5005'
48 | volumes:
49 | - '/srv/gitlab/config:/etc/gitlab'
50 | - '/srv/gitlab/logs:/var/log/gitlab'
51 | - '/srv/gitlab/data:/var/opt/gitlab'
--------------------------------------------------------------------------------
/production/traefik/dynamic-conf.yml:
--------------------------------------------------------------------------------
1 | http:
2 | routers:
3 | ############ Routers section ############
4 | api:
5 | entryPoints:
6 | - "web-secure"
7 | rule: "Host(`traefik.company.ru`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
8 | service: "api@internal"
9 | middlewares:
10 | - "apiAuth"
11 | tls:
12 | certResolver: "letsencrypt"
13 | options: "default"
14 |
15 | route-redirect-to-https:
16 | entryPoints:
17 | - "web"
18 | rule: "HostRegexp(`{any:.*}`)"
19 | middlewares:
20 | - "to-https"
21 | - "security-headers"
22 | service: "dummy"
23 |
24 | route-to-gitlab:
25 | entryPoints:
26 | - "web-secure"
27 | rule: "Host(`gitlab.company.ru`)"
28 | middlewares:
29 | - "security-headers"
30 | service: "gitlab"
31 | tls:
32 | certResolver: "letsencrypt"
33 | options: "default"
34 |
35 | route-to-registry:
36 | entryPoints:
37 | - "web-secure"
38 | rule: "Host(`registry.company.ru`)"
39 | middlewares:
40 | - "security-headers"
41 | service: "registry"
42 | tls:
43 | certResolver: "letsencrypt"
44 | options: "default"
45 |
46 | route-to-production:
47 | priority: 1
48 | entryPoints:
49 | - "web-secure"
50 | rule: "Host(`company.ru`)"
51 | middlewares:
52 | - "security-headers"
53 | - "WebSockets-header-proto"
54 | service: "production"
55 | tls:
56 | certResolver: "letsencrypt"
57 | options: "default"
58 |
59 | ############ Middlewares section ############
60 | middlewares:
61 | to-https:
62 | redirectScheme:
63 | scheme: "https"
64 | permanent: true
65 |
66 | # Improving SSL security ratings https://community.containo.us/t/improving-the-ssl-rating/939/3
67 | security-headers:
68 | headers:
69 | accessControlAllowMethod:
70 | - GET
71 | - OPTIONS
72 | - PUT
73 | accessControlAllowOrigin: "origin-list-or-null"
74 | accessControlMaxAge: 100
75 | browserXssFilter: true
76 | contentTypeNosniff: true
77 | forceSTSHeader: true
78 | frameDeny: true
79 | sslRedirect: true
80 | stsIncludeSubdomains: true
81 | stsPreload: true
82 | # С этой настройкой не дружит gitlab =(, он подгружает какие-то сторонние файлы
83 | # contentSecurityPolicy: "default-src 'self' 'unsafe-inline'"
84 | customFrameOptionsValue: "SAMEORIGIN"
85 | referrerPolicy: "same-origin"
86 | featurePolicy: "vibrate 'self'"
87 | stsSeconds: 315360000
88 |
89 | only-internal:
90 | ipWhiteList:
91 | sourceRange:
92 | - "192.168.88.0/24"
93 | ipStrategy:
94 | depth: 2
95 |
96 | WebSockets-header-proto:
97 | headers:
98 | customRequestHeaders:
99 | X-Forwarded-Proto: "https"
100 |
101 | apiAuth:
102 | basicAuth:
103 | users:
104 | # https://www.web2generators.com/apache-tools/htpasswd-generator
105 | # admin:password
106 | - "admin:$apr1$vq27zuno$9UqIsae/7T4Jznsm9p7Tn."
107 |
108 | ############ Services section ############
109 | services:
110 | gitlab:
111 | loadBalancer:
112 | servers:
113 | - url: "http://192.168.88.4"
114 | registry:
115 | loadBalancer:
116 | servers:
117 | - url: "http://192.168.88.4:5000"
118 |
119 | production:
120 | loadBalancer:
121 | servers:
122 | - url: "http://192.168.88.2:8000"
123 |
124 | # https://github.com/containous/traefik/issues/4863
125 | dummy:
126 | loadBalancer:
127 | servers:
128 | - url: ""
129 |
130 | tcp:
131 | routers:
132 | route-to-gitlab-ssh:
133 | entryPoints:
134 | - "ssh-gitlab"
135 | # https://docs.traefik.io/routing/routers/#rule_1
136 | # Незащищенные множественные TCP-соединения разрешаются только добавлением своих entrypoint-ов.
137 | rule: "HostSNI(`*`)"
138 | service: "gitlab-ssh"
139 | services:
140 | gitlab-ssh:
141 | loadBalancer:
142 | servers:
143 | - address: "192.168.88.4:1023"
144 |
145 | tls:
146 | options:
147 | default:
148 | sniStrict: true
149 | cipherSuites:
150 | - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
151 | - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
152 | - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
153 | - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
154 | - TLS_AES_128_GCM_SHA256
155 | - TLS_AES_256_GCM_SHA384
156 | - TLS_CHACHA20_POLY1305_SHA256
157 | - TLS_FALLBACK_SCSV
158 | # алгоритм, чтобы MS EDGE не плевался
159 | - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
160 | certificates:
161 | - certFile: /certs/dev.company.ru.cert.pem
162 | keyFile: /certs/dev.company.ru.privkey.pem
163 | stores:
164 | - default
165 | stores:
166 | default:
167 | defaultCertificate:
168 | certFile: /certs/dev.company.ru.cert.pem
169 | keyFile: /certs/dev.company.ru.privkey.pem
170 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Демо-проект с описанием инфраструктуры
2 |
3 | 
4 |
5 | ## Минимальные требования для построения:
6 | * Наличие каких-либо мощностей в распоряжении. Может быть свой сервер, а может быть и облачная инфраструктура;
7 | * Знание вашего приложения, как оно работает, как сейчас разворачивается;
8 | * Базовые знания сетей, git, Linux, Docker, GitLab, Traefik.
9 |
10 | ## Предупреждение
11 | * Данное инфраструктурное решение является примером для понимания основных принципов, нагрузочных тестирований не проводилось.
12 | Очень многое зависит от железа и архитектуры приложения. Для больших нагрузок, повышения надёжности и работы в облаке
13 | рекомендуется рассмотреть Enterprise версии Traefik и GitLab.
14 | * Пример приводится для веб-приложений, но вполне может быть адаптирован под ваши нужды.
15 |
16 | ## Описание
17 | * В примере разработка предполагается по минимальной классической схеме: есть *feature*-ветки, *master* и *development*.
18 | В *development* ведется интеграция всех *feature*-веток, которые удаляются после успешного слияния.
19 | Стабильные релизы идут в *master*.
20 | * Есть 3 сервера [**Production**], [**Staging**] и [**Services**]. Могут быть физическими или виртуальными машинами,
21 | количество может быть меньше и больше, может быть всё в облаке. Приведена наиболее эффективная конфигурация с точки зрения надёжность/цена.
22 | На сервере [**Services**] установлен **GitLab** а также второстепеннные сервисы
23 | (мониторинг, docker registry: Portainer, ELK, Harbor, etc), которые и будем называть **Services**.
24 | В данном примере их настройка не рассматривается. Все приложения работают в Docker-контейнерах.
25 | **GitLab** лучше установить отдельно, зависит от располагаемых мощностей.
26 | * Реверс-прокси **Traefik** собирает информацию о запущенных динамических DNS-именах для **.dev.company.ru*,
27 | подключившись к докеру [**Staging**] по TCP. Также автоматически
28 | получает SSL сертификаты для приложения на [**Production**]. Wildcard (WC) сертификат **dev.company.ru*
29 | получается с помощью отдельного контейнера **letsencrypt-dns**, если ваш DNS-провайдер не поддерживается в **Traefik**.
30 | Способ получения WC сертификатов через **Traefik** в примере не рассмотрен.
31 | **Traefik** использует этот или самостоятельно полученный сертификат, обрезает SSL от клиентов и перенаправляет http
32 | запросы по доменным именам на соответствующие сервисы. Работает на [**Production**] вместе с основным приложением.
33 | * **GitLab** с помощью **GitLab-runner**-ов, установленных на остальных ВМ, по Merge Request-ам (МР) на
34 | ветки *dev* и *master*, управляет запущенными докер-образами на [**Staging**] и [**Production**] согласно .gitlab-ci.yml проектов.
35 | * Сборка, тест и стейджинг происходят на [**Staging**].
36 | * **GitLab** также работает как Docker Registry (registry.company.ru), где хранятся образы приложений.
37 | * Сами **GitLab**, **Traefik** и **Gitlab-runner**-ы также работают в docker-контейнерах, что позволяет легко обновлять и переносить инфраструктуру.
38 |
39 | ## Принятые решения
40 | * Statefull-данные докер-приложений храним в `/srv/<проект>/<имя контейнера>/`
41 | * **Gitlab-runner** запускаем в **docker** и берем *docker-executor*.
42 | * Билдить и тестировать придется, расшаривая docker.sock для executor-ов **gitlab-runner**-а.
43 | * Удаленные подключения к докеру по TCP с сертификатом.
44 | * Используем overlay2 драйвер для докера.
45 | * SSH для git на **GitLab** будет идти через 1023 порт, чтобы не конфликтовать с доступами к машинам
46 |
47 |
48 | ## Разворачивание инфраструктуры
49 | ### На каждом сервере
50 | #### Установка docker и docker-compose
51 | * Docker-compose устанавливаем ТОЛЬКО официальным методом, иначе можно влететь в ошибку (ссылка на решение в Issue):
52 | https://github.com/docker/compose/issues/6023#issuecomment-427896175
53 | * Docker-compose будем запускать в контейнере с помощью официального скрипта-обёртки:
54 | https://docs.docker.com/compose/install/#install-as-a-container
55 | ```shell script
56 | # установка docker
57 | curl -fsSL https://get.docker.com -o get-docker.sh && sudo sh get-docker.sh
58 | # Переход на overlay2
59 | # https://docs.docker.com/storage/storagedriver/overlayfs-driver/
60 | sudo touch /etc/docker/daemon.json && sudo nano /etc/docker/daemon.json
61 | #######################################
62 | # добавить текст в файл
63 | {
64 | "storage-driver": "overlay2"
65 | }
66 | #######################################
67 | sudo systemctl restart docker
68 | # docker-compose
69 | sudo curl -L --fail https://github.com/docker/compose/releases/download/1.25.3/run.sh -o /usr/local/bin/docker-compose \
70 | && sudo chmod +x /usr/local/bin/docker-compose
71 | # Установка временной зоны
72 | sudo dpkg-reconfigure tzdata
73 | sudo unlink /etc/localtime && sudo ln -s /usr/share/zoneinfo/Europe/Volgograd /etc/localtime
74 | ```
75 |
76 | #### Доступ к docker по TCP с TLS сертификатом
77 | * Пример для [**Staging**] - там необходимо это проделать для автоматического подтягивания динамических окружений
78 | в **Traefik**. Для остальных серверов можно сделать только для удобства менеджмента (например, Portainer или в IDE).
79 | Для этого нужно заменить IP адрес и путь в конце.
80 | * Выполняем по гайду (по нему придется раз в год обновлять, можно увеличить количество *-days 365*):
81 | https://docs.docker.com/engine/security/https/
82 | * Когда напарываемся, что нужен *.RND* файл:
83 | ```shell script
84 | # https://crypto.stackexchange.com/questions/68919/is-openssl-rand-command-cryptographically-secure
85 | openssl rand -hex 32 > ~/.rnd
86 | ```
87 | Для того, чтобы в итоге запустить dockerd с сертификатом, необходимо перенастроить запуск с помощью systemd:
88 | * https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-socket-option
89 | * https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-configuration-file
90 | * https://docs.docker.com/config/daemon/systemd/#custom-docker-daemon-options
91 | * https://docs.docker.com/install/linux/linux-postinstall/#configuring-remote-access-with-systemd-unit-file
92 |
93 | ##### Итоговая последовательность команд
94 | ```shell script
95 | mkdir docker-certs \
96 | && cd docker-certs \
97 | && openssl genrsa -aes256 -out ca-key.pem 4096
98 | #######################################
99 | # запомнить, вводить в дальнейшем по запросам
100 | passphrase: *****
101 | #######################################
102 |
103 | openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem
104 | #######################################
105 | # ввод данных
106 | RU
107 | Volgograd-district
108 | Volgograd
109 | Company LLC
110 | IT
111 | 192.168.88.3
112 | mail@company.ru
113 | #######################################
114 |
115 | openssl genrsa -out server-key.pem 4096 \
116 | && cd ~ \
117 | && openssl rand -hex 32 > ~/.rnd \
118 | && cd docker-certs \
119 | && openssl req -subj "/CN=192.168.88.3" -sha256 -new -key server-key.pem -out server.csr \
120 | && echo subjectAltName = DNS:192.168.88.3,IP:192.168.88.3 >> extfile.cnf \
121 | && echo extendedKeyUsage = serverAuth >> extfile.cnf \
122 | && openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem \
123 | -CAcreateserial -out server-cert.pem -extfile extfile.cnf
124 |
125 | openssl genrsa -out key.pem 4096 \
126 | && openssl req -subj '/CN=client' -new -key key.pem -out client.csr \
127 | && echo extendedKeyUsage = clientAuth > extfile-client.cnf \
128 | && openssl x509 -req -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem \
129 | -CAcreateserial -out cert.pem -extfile extfile-client.cnf
130 |
131 | rm -v client.csr server.csr extfile.cnf extfile-client.cnf
132 | # Скопировать ca.pem cert.pem key.pem для подключения клиента.
133 | cd ~ \
134 | && mkdir .docker \
135 | && sudo mv docker-certs/* .docker/ \
136 | && rm -rf docker-certs
137 | sudo systemctl edit docker.service
138 | #######################################
139 | # добавить в файл
140 | [Service]
141 | ExecStart=
142 | ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2376 --tlsverify --tlscacert=/home/staging/.docker/ca.pem --tlscert=/home/staging/.docker/server-cert.pem --tlskey=/home/staging/.docker/server-key.pem
143 | #######################################
144 | sudo systemctl daemon-reload && sudo systemctl restart docker.service \
145 | && sudo chmod 600 ~/.docker/* \
146 | && rm ~/.rnd
147 | ```
148 |
149 |
150 | ### GitLab
151 | #### Установка с помощью docker-compose в докере
152 | * https://docs.gitlab.com/omnibus/docker/#install-gitlab-using-docker-compose
153 | Необходимо перекинуть файл `docker-compose.services.yml` на [**Services**].
154 | ```shell script
155 | # Создаём структуру папок
156 | sudo mkdir -p /srv/services/gitlab/{config,data,logs}
157 | sudo docker-compose -f docker-compose.services.yml -p gitlab up -d
158 | ```
159 |
160 | #### Установка GitLab Omnibus прямо на машину
161 | * Конфигурация **GitLab** со своим **Registry** для работы c SSL reverse-proxy:
162 | ```shell script
163 | sudo nano /etc/gitlab/gitlab.rb
164 | #######################################
165 | external_url "https://gitlab.company.ru"
166 | nginx['proxy_set_headers'] = {
167 | "Host" => "$http_host",
168 | "X-Real-IP" => "$remote_addr",
169 | "X-Forwarded-For" => "$proxy_add_x_forwarded_for",
170 | "X-Forwarded-Proto" => "https",
171 | "X-Forwarded-Ssl" => "on"
172 | }
173 | nginx['listen_port'] = 80
174 | nginx['listen_https'] = false
175 | gitlab_rails['time_zone'] = 'Europe/Volgograd'
176 | gitlab_rails['trusted_proxies'] = ['192.168.88.1', '192.168.88.2']
177 | registry_external_url 'https://registry.company.ru'
178 | registry['enable'] =true
179 | registry_nginx['enable'] = true
180 | registry_nginx['proxy_set_headers'] = {
181 | "Host" => "$http_host",
182 | "X-Real-IP" => "$remote_addr",
183 | "X-Forwarded-For" => "$proxy_add_x_forwarded_for",
184 | "X-Forwarded-Proto" => "https",
185 | "X-Forwarded-Ssl" => "on"
186 | }
187 | registry_nginx['listen_port'] = 5005
188 | registry_nginx['listen_https'] = false
189 | nginx['real_ip_trusted_addresses'] = ['192.168.88.1', '192.168.88.2']
190 | nginx['real_ip_header'] = 'X-Forwarded-For'
191 | nginx['real_ip_recursive'] = 'on'
192 | sudo gitlab-ctl reconfigure
193 | # nginx требует отдельного перезапуска
194 | sudo gitlab-ctl hup nginx
195 | ```
196 |
197 |
198 | ### Production
199 | #### Поднимаем Traefik
200 | Пока мы не подняли реверс-прокси, если зайти на **GitLab** напрямую по IP адресу, то
201 | он все запросы редиректит на https, согласно нашим настройкам, сертификата которого пока нет.
202 | * На [**Production**] необходимо переместить папку `production` и `docker-compose.prod.yml`
203 | из примера, а также TLS сертификаты к докеру [**Staging**].
204 | Предполагаем, что поместили всё в папку `/home//project`
205 | ```shell script
206 | cd ~/project
207 | # Создаём структуру папок для возможности скопировать файлы далее
208 | sudo mkdir -p /srv/{gitlab-runner/config,demo-prj/{backend/media,db/data},letsencrypt-dns,traefik/{certs/staging,letsencrypt,logs}}
209 | # перекидываем файлы конфигураций traefik
210 | sudo cp -r ~/project/production/* /srv/
211 | sudo docker-compose -f docker-compose.prod.yml -p prod up -d traefik
212 | #######################################
213 | # Или можно вручную для тестов:
214 | sudo docker pull traefik:v2
215 | # шаг, если обновляем вручную:
216 | # sudo docker stop traefik && sudo docker rm traefik
217 | sudo docker run -d \
218 | --restart always \
219 | --name traefik \
220 | --volume /srv/traefik/traefik.yml:/etc/traefik/traefik.yml \
221 | --volume /srv/traefik/dynamic-conf.yml:/etc/traefik/dynamic-conf.yml \
222 | --volume /srv/traefik/letsencrypt:/letsencrypt \
223 | --volume /srv/traefik/certs:/certs \
224 | --volume /srv/traefik/logs:/logs \
225 | --volume /usr/share/zoneinfo:/usr/share/zoneinfo:ro \
226 | -p 8080:8080 \
227 | -p 80:80 \
228 | -p 443:443 \
229 | -p 1023:1023 \
230 | -e TZ=Europe/Volgograd \
231 | traefik:latest
232 | #######################################
233 | ```
234 |
235 | * На этом этапе нужно зайти на **GitLab** и получить токен проекта из Settings->CI/CD.
236 | * При первом входе на **GitLab** нужно указать пароль пользователя `root`, есть проверка на сложность.
237 | * Заодно нужно установить в проекте переменные
238 | * $DNS_PROVIDER_API_TOKEN и $LETSENCRYPT_EMAIL для контейнера letsencrypt-dns (см. ниже) или другие, для тех же целей у **Traefik**.
239 | * $TZ, https://gitlab.com/gitlab-com/support-forum/issues/4051
240 |
241 | #### Поднимаем gitlab-runner
242 | ```shell script
243 | cd ~/project
244 | sudo docker-compose -f docker-compose.prod.yml -p prod up -d gitlab-runner
245 | # Или вручную:
246 | sudo docker run -d --restart always --name gitlab-runner \
247 | -v /srv/gitlab-runner/config:/etc/gitlab-runner \
248 | -v /var/run/docker.sock:/var/run/docker.sock \
249 | gitlab/gitlab-runner:latest
250 | ```
251 | #### Регистрируем gitlab-runner
252 | * Доступ к `/srv` нужен, если захотим управлять другими приложениями прямо из проекта на **GitLab**
253 | (например, удалить неисправную БД или обновить версию **Traefik**)
254 | * Расшаривание `/var/run/docker.sock` позволяет запускать контейнеры на хосте командой `docker`
255 | из раннера, самого запущенного в Docker
256 | ```shell script
257 | # Конфигурируем раннер с помощью другого контейнера, который будет запущен один раз.
258 | # Требуется docker.sock для использования в .gitlab-ci.yml образа с docker и docker-compose:
259 | # https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-socket-binding
260 | sudo docker run --rm -v /srv/gitlab-runner/config:/etc/gitlab-runner gitlab/gitlab-runner:latest register \
261 | --non-interactive \
262 | --executor "docker" \
263 | --docker-image "docker:stable" \
264 | --docker-volumes "/var/run/docker.sock:/var/run/docker.sock" \
265 | --docker-volumes "/srv:/srv" \
266 | --url "https://gitlab.company.ru/" \
267 | --registration-token "" \
268 | --description "Production server gitlab runner" \
269 | --tag-list "production" \
270 | --run-untagged="false" \
271 | --locked="false" \
272 | --access-level="not_protected"
273 | ```
274 |
275 | #### Опционально letsencrypt-dns
276 | Нужен для получения WC сертификата для динамических dns-имен тестируемых сборок.
277 | Уже можно развернуть автоматом через **GitLab**, если создан проект с инфраструктурой.
278 |
279 | Для поднятия в тестовом режиме вручную:
280 | ```shell script
281 | # -d - detached режим, если устанавливаем время ожидания обновления DNS-записей большим
282 | # (например, вы используете отечественного DNS-провайдера как Яндекс)
283 | sudo docker run -d \
284 | --name letsencrypt-dns \
285 | --restart always \
286 | --volume /srv/letsencrypt-dns:/etc/letsencrypt \
287 | --env 'LETSENCRYPT_USER_MAIL=<почта пользователя letsencrypt>' \
288 | --env 'LEXICON_PROVIDER=yandex' \
289 | --env 'LEXICON_SLEEP_TIME=1800' \
290 | --env 'LEXICON_PROVIDER_OPTIONS=--auth-token=<токен провайдера DNS>' \
291 | --env 'LETSENCRYPT_STAGING=true' \
292 | --env 'TZ=Europe/Volgograd' \
293 | adferrand/letsencrypt-dns:latest
294 | # Смотрим /srv/letsencrypt-dns/certs/ - если всё в порядке, появятся сертификаты.
295 | # Тогда очистить папки, перезапустить контейнер, убрав переменную LETSENCRYPT_STAGING.
296 | # Теперь сертификаты будут получены настоящие.
297 | # Staging сервера Let's Encrypt используются, чтобы не тратить количество выпусков сертификатов
298 | # https://letsencrypt.org/docs/rate-limits/ (wildcard 5 раз в неделю можно запросить)
299 | # Логи смотреть sudo docker logs letsencrypt-dns
300 | ```
301 | Полученные сертификат и приватный ключ переместить в папку `/srv/traefik/certs`,
302 | а также переименовать в *dev.company.ru.cert.pem* и *dev.company.ru.privkey.pem* (см. *dynamic-conf.yml*).
303 | После этого необходимо перезапустить **Traefik**, чтобы он подхватил сертификаты
304 | ```shell script
305 | sudo cp /srv/letsencrypt-dns/live/dev.company.ru/cert.pem /srv/traefik/certs/dev.company.ru.cert.pem && sudo cp /srv/letsencrypt-dns/live/dev.company.ru/privkey.pem /srv/traefik/certs/dev.company.ru.privkey.pem
306 | sudo docker restart traefik
307 | ```
308 |
309 | ### Staging
310 | #### Поднимаем gitlab-runner
311 | ```shell script
312 | cd ~/project
313 | # Создаём структуру папок
314 | sudo mkdir -p /srv/gitlab-runner/{cache,config,jobs}
315 | sudo docker-compose -f docker-compose.staging.yml -p staging up -d gitlab-runner
316 | # Или вручную:
317 | sudo docker run -d --restart always --name gitlab-runner \
318 | -v /srv/gitlab-runner/config:/etc/gitlab-runner \
319 | -v /var/run/docker.sock:/var/run/docker.sock \
320 | gitlab/gitlab-runner:latest
321 | ```
322 |
323 | #### Регистрируем gitlab-runner
324 | Небольшое отличие от **Production**:
325 | Монтирование `/srv/gitlab-runner/shared_jobs/` пригодится, чтобы можно было вытаскивать данные из контейнеров
326 | после тестов (например, скриншоты с тестов **Selenium** и др. артефакты).
327 | ```shell script
328 | sudo docker run --rm -v /srv/gitlab-runner/config:/etc/gitlab-runner gitlab/gitlab-runner:latest register \
329 | --non-interactive \
330 | --executor "docker" \
331 | --docker-image "docker:stable" \
332 | --docker-volumes "/var/run/docker.sock:/var/run/docker.sock" \
333 | --docker-volumes "/srv/gitlab-runner/shared_jobs:/shared_jobs" \
334 | --docker-volumes "/srv:/srv" \
335 | --url "https://gitlab.company.ru" \
336 | --registration-token "" \
337 | --description "Staging server gitlab runner" \
338 | --tag-list "staging" \
339 | --run-untagged="false" \
340 | --locked="false" \
341 | --access-level="not_protected"
342 | # https://habr.com/ru/post/449910
343 | # Если машина, которая будет собирать и тестировать, мощная, можно добавить параллельных сборок
344 | sudo nano /srv/gitlab-runner/etc/gitlab-runner/config.toml
345 | #######################################
346 | concurrent=20
347 | [[runners]]
348 | request_concurrency = 10
349 | #######################################
350 |
351 | ```
352 |
353 | ## Что можно сразу улучшить, TODO:
354 | * Добавить **fail2ban** для **Traefik**
355 | Скорее всего потребуется ещё настроить logrotate (т.к. надо логировать TCP - нет фильтрации)
356 | * https://github.com/crazy-max/docker-fail2ban/tree/master/examples/jails/traefik
357 | * https://gist.github.com/acundari/9bdcf2ba0c0f8a4bf59a21d06da35612
358 | * https://stackoverflow.com/questions/52123355/how-to-implement-fail2ban-with-traefik
359 | * https://www.reddit.com/r/docker/comments/axmuj6/nginxtraefik_and_fail2ban_lost_on_how_to_configure/
360 | * Автоматизировать обслуживание GitLab Registry (удаление устаревших тэгов в каждом проекте, чистка неиспользуемых слоёв)
361 | * ...или использовать для Docker Registry вместо GitLab Harbor или вместе со сборкой - Werf от Флант
362 | * Автоматизировать создание WC сертификатов для [**Staging**] и передачу их **Traefik**.
363 | Для простоты можно настроить перемещение по расписанию.
364 |
365 | ## Материалы
366 |
367 | ### Traefik
368 | * https://docs.traefik.io/
369 |
370 | #### Docker with Certbot + Lexicon to provide Let's Encrypt SSL certificates validated by DNS challenges
371 | * Приходится использовать, так как **Traefik** (точнее, входящая в его состав библиотека LEGO -
372 | https://github.com/go-acme/lego/pull/278) пока не может работать с Yandex DNS API.
373 | * https://github.com/adferrand/docker-letsencrypt-dns
374 |
375 | ### Альтернативный вариант reverse proxy + SSL на nginx
376 | * https://habr.com/ru/post/328048/
377 | * https://habr.com/ru/post/445448/
378 | * https://github.com/jwilder/nginx-proxy
379 | * https://github.com/JrCs/docker-letsencrypt-nginx-proxy-companion
380 |
381 | ### GitLab
382 | * https://docs.gitlab.com/omnibus/docker
383 | #### GitLab SSL config
384 | * https://docs.gitlab.com/omnibus/settings/ssl.html
385 | * Конфигурация за reverse-proxy https://docs.gitlab.com/omnibus/settings/nginx.html#supporting-proxied-ssl
386 | #### GitLab Registry
387 | * https://docs.gitlab.com/ce/administration/container_registry.html#configure-container-registry-under-its-own-domain
388 | * Удаление образов: https://docs.gitlab.com/omnibus/maintenance/#container-registry-garbage-collection
389 | #### Gitlab-runner
390 | * Установка с помощью образа докера
391 | * https://docs.gitlab.com/runner/install/docker.html
392 | * Использование Docker Executor https://docs.gitlab.com/runner/executors/docker.html
393 | * Использование SSH Executor https://docs.gitlab.com/runner/executors/ssh.html
394 | * Регистрация раннера https://docs.gitlab.com/runner/register/index.html#docker
395 | * Создание образов Docker с помощью GitLab CI/CD https://docs.gitlab.com/ce/ci/docker/using_docker_build.html
396 | * Создание образов Docker внутри Docker без использования priveleged mode и кэшированием в registry
397 | (полезно для работы в облаке) https://docs.gitlab.com/ce/ci/docker/using_kaniko.html
398 | * Пригодится для создания файла конфигурации https://docs.gitlab.com/runner/configuration/advanced-configuration.html
399 | * CLI https://docs.gitlab.com/runner/commands/README.html
400 |
401 | ### Docker
402 | * Действия после установки https://docs.docker.com/install/linux/linux-postinstall/
403 | * docker-compose файл https://docs.docker.com/compose/reference/overview/
404 | * Полезно использовать при отладке https://docs.docker.com/compose/reference/config/
405 | * Подключение к удаленному **Docker** с сертификатами (TCP + TLS): https://docs.docker.com/engine/security/https/
406 |
407 | ### Прочее полезное
408 | * Анализ докер-образов: https://github.com/wagoodman/dive
409 | ```shell script
410 | # Команда для анализа docker образов (утилита запускается в докере)
411 | sudo docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock wagoodman/dive:latest adferrand/letsencrypt-dns:latest
412 | ```
413 | * Генератор конфигов различных серверов для работы с SSL: https://ssl-config.mozilla.org/#server=traefik&server-version=2.1&config=intermediate
414 |
415 | * Хорошие гайды, но со старым **Traefik**:
416 | * https://community.hetzner.com/tutorials/gitlab-server-with-docker#step-5-prepare-the-registry
417 | * https://www.davd.io/byecloud-gitlab-with-docker-and-traefik/
418 |
419 | * GitLab Shell Runner. Конкурентный запуск тестируемых сервисов при помощи **docker-compose** https://habr.com/ru/post/449910
420 |
421 | * https://romantelychko.com/blog/1300/ Пригодится когда-нибудь
422 |
423 | * Группы в телеграмме:
424 | * https://t.me/ru_gitlab
425 | * https://t.me/ru_docker
426 | * https://t.me/PostgreSQL_1C_Linux
427 | * https://t.me/werf_ru
428 |
429 | * Доклады Дмитрия Столярова из компании "Флант"
430 |
--------------------------------------------------------------------------------