├── 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 | ![infrastructure_diagram](img/scheme.png) 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 | --------------------------------------------------------------------------------