Имеется у нас Jenkins, который запускает в Docker-контейнерах свои задачи.
Со временем столкнулись с тем, что инстанс t2.2xlarge (8 CPU, 32 RAM) при пиковых нагрузках уже не справляется — забиваются и память, и процессорное время.
Варианты — либо продолжать вертикальный скейлинг одного мастер-инстанса, и на нём дальше в Docker запускать джобы — или вынести запуск джоб на внешние слейвы.
Сейчас у нас есть три внешних слейва для Jenkins — Android-билды выполняются на машинке в офисе, на которой установлен Android Studio (128 ГБ памяти, кажется), и пачка MacMini для iOS билдов, плюс выделенный AWS EC2 для запуска UI-тестов нашей QA-команды.
Добавим к этому зоопарку запуск слейвов в Kubernetes-кластере.
Используем AWS Elastic Kubernetes Service и Kubernetes Plugin для Jenkins.
Содержание
Jenkins-master: подготовка
Для тестов создадим ЕС2 с Ubuntu 20.04, на нём в Docker поднимем тестовый мастер-инстанс Jenkins, в котором установим плагин.
Docker install
Запускаем ЕС2, подключаемся, устанавливаем Docker:
[simterm]
root@ip-10-0-4-6:/home/ubuntu# apt update && apt -y upgrade root@ip-10-0-4-6:/home/ubuntu# curl https://get.docker.com/ | bash
[/simterm]
И Docker Compose.
Находим последнюю версию в Github, на момент написания это 1.28.4, и загружаем его:
[simterm]
root@ip-10-0-4-6:/home/ubuntu# curl -L "https://github.com/docker/compose/releases/download/1.28.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose root@ip-10-0-4-6:/home/ubuntu# chmod +x /usr/local/bin/docker-compose root@ip-10-0-4-6:/home/ubuntu# docker-compose --version docker-compose version 1.28.4, build cabd5cfb
[/simterm]
Запуск Jenkins в Docker
На хосте создаём каталог, в котором Jenkins будет хранить свои данные:
[simterm]
root@ip-10-0-4-6:/home/ubuntu# mkdir jenkins_home
[/simterm]
Пишем Docker Compose файл:
version: '3.5'
networks:
jenkins:
name: jenkins
services:
jenkins:
user: root
image: jenkins/jenkins:2.249.3
networks:
- jenkins
ports:
- '8080:8080'
- '50000:50000'
volumes:
- /home/ubuntu/jenkins_home/:/var/lib/jenkins
- /var/run/docker.sock:/var/run/docker.sock
- /usr/bin/docker:/usr/bin/docker
- /usr/lib/x86_64-linux-gnu/libltdl.so.7:/usr/lib/x86_64-linux-gnu/libltdl.so.7
environment:
- JENKINS_HOME=/var/lib/jenkins
- JAVA_OPTS=-Duser.timezone=Europe/Kiev
logging:
driver: "journald"
Запускаем:
[simterm]
root@ip-10-0-4-6:/home/ubuntu# docker-compose -f jenkins-compose.yaml up
[/simterm]
И открываем в браузере:
Administrator password есть в выводе Docker Compose при старте сервера, либо можно его взять в самом контейнере в файле /var/lib/jenkins/secrets/initialAdminPassword:
[simterm]
root@ip-10-0-4-6:/home/ubuntu# docker exec -ti ubuntu_jenkins_1 cat /var/lib/jenkins/secrets/initialAdminPassword 15d***730
[/simterm]
Логинимся, выполняем начальную установку:
Создаём юзера:
Завершаем установку, и переходим к установке плагина.
Jenkins Slaves in Kubernetes
Находим плагин Kubernetes:
Устанавливаем, переходим в Manage Nodes and Clouds > Configure Clouds:
Выбираем Kubernetes:
Задаём URL API-сервера и Namespace:
Jenkins ServiceAccount
Создаём Namespace и ServiceAccount, под которым будем авторизировать наш Jenkins-мастер.
На Production-инстансе можно сделать через EC2 Instance Profile, которому будет подключаться нужная IAM-роль.
Тут же описываем Kubernetes RoleBinding на дефолтную админ-роль (или пишем свою роль) в нашем неймспейсе dev-1-18-devops-jenkins-slaves-ns:
--- apiVersion: v1 kind: Namespace metadata: name: dev-1-18-devops-jenkins-slaves-ns --- apiVersion: v1 kind: ServiceAccount metadata: name: jenkins-slaves-service-account namespace: dev-1-18-devops-jenkins-slaves-ns --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: jenkins-slaves-rolebinding namespace: dev-1-18-devops-jenkins-slaves-ns roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: admin subjects: - kind: ServiceAccount name: jenkins-slaves-service-account namespace: dev-1-18-devops-jenkins-slaves-ns
Деплоим:
[simterm]
$ kubectl apply -f jenkins-slaves-sa.yaml namespace/dev-1-18-devops-jenkins-slaves-ns created serviceaccount/jenkins-slaves-service-account created rolebinding.rbac.authorization.k8s.io/jenkins-slaves-rolebinding created
[/simterm]
Jenkins ServiceAccount и kubeconfig
Надо создать kubeconfig, который будет использовать этот ServiceAccount.
Для этого нужны Cluster ARN, Certificate authority, адрес API-сервера, и JWT-токен нашего ServiceAccount.
Находим секрет этого ServiceAccount:
[simterm]
$ kubectl -n dev-1-18-devops-jenkins-slaves-ns get sa jenkins-slaves-service-account -o jsonpath='{.secrets[0].name}'
jenkins-slaves-service-account-token-jsbb7
[/simterm]
Certificate authority и Cluster ARN берём в админке Амазона:
Получаем токен из секрета, декриптим из base64:
[simterm]
$ kubectl -n dev-1-18-devops-jenkins-slaves-ns get secret jenkins-slaves-service-account-token-jsbb7 -o jsonpath='{.data.token}' | base64 --decode
eyJ...s7w
[/simterm]
Пишем kubeconfig, например в файл jenkins-dev-1-18-kubeconfig.yaml:
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0...LQo=
server: https://676***892.gr7.us-east-2.eks.amazonaws.com
name: arn:aws:eks:us-east-2:534***385:cluster/bttrm-eks-dev-1-18
contexts:
- context:
cluster: arn:aws:eks:us-east-2:534***385:cluster/bttrm-eks-dev-1-18
user: jenkins-slaves-service-account
namespace: dev-1-18-devops-jenkins-slaves-ns
name: jenkins-slaves-service-account@bttrm-dev-1-18
current-context: jenkins-slaves-service-account@bttrm-dev-1-18
kind: Config
users:
- name: jenkins-slaves-service-account
user:
token: ZXl...N3c=
Проверяем доступ:
[simterm]
$ kubectl -n dev---kubeconfig ../jenkins-dev-1-18-kubeconfig.yaml auth can-i get pod yes
[/simterm]
Jenkins Kubernetes Credentials
Возвращаемся к Jenkins, добавляем Credential:
Проверяем подключение — кликаем Test Connection, получаем ответ Connected to Kubernetes 1.18+:
Сохраняем.
Jenkins Slaves Pod Template
Переходим в Pod templates:
Заполняем шаблон пода и дефолтного контейнера, в качестве образа используем jenkinsci/jnlp-slave:
Jenkins Job
Создаём тестовую джобу, тип Pipeline:
Пишем скрипт:
podTemplate {
node(POD_LABEL) {
stage('Run shell') {
sh 'echo hello world'
}
}
}
Запускаем, смотрим логи Jenkins:
[simterm]
... jenkins_1 | 2021-02-26 08:36:32.226+0000 [id=277] INFO hudson.slaves.NodeProvisioner#lambda$update$6: k8s-1-b5j7g-glscn-v0tfz provisioning successfully completed. We have now 2 computer(s) jenkins_1 | 2021-02-26 08:36:32.522+0000 [id=276] INFO o.c.j.p.k.KubernetesLauncher#launch: Created Pod: dev-1-18-devops-jenkins-slaves-ns/k8s-1-b5j7g-glscn-v0tfz ...
[/simterm]
Под создаётся:
[simterm]
$ kubectl --kubeconfig ../jenkins-dev-1-18-kubeconfig.yaml get pod NAME READY STATUS RESTARTS AGE k8s-1-b5j7g-glscn-v0tfz 0/1 ContainerCreating 0 12s
[/simterm]
И выполнение джобы:
Docker в Docker через Docker в Kubernetes
Наш Jenkins работает в Docker-контейнере.
И свои билды он тоже запускает в Docker-контейнерах. Способ испытанный, удобный — не захламляем хост-машину библиотеками, билды независимы, девелоперы сами могут настраивать свои среды сборок.
Теперь нам надо повторить тоже самое, но со слейвами в Kubernetes.
В Jenkins устанавливаем Docker pipeline plugin:
И пишем пайплайн.
Тут нам понадобится новый образ — docker.dind, а для него — новый шаблон пода.
Можем создать его в UI, как делали в начале, можем описать прямо в пайплайне используя podTemplate.
Например — соберём образ NGINX:
podTemplate(yaml: '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: docker
image: docker:19.03.1-dind
securityContext:
privileged: true
env:
- name: DOCKER_TLS_CERTDIR
value: ""
''') {
node(POD_LABEL) {
git 'https://github.com/nginxinc/docker-nginx.git'
container('docker') {
sh 'docker version && cd stable/alpine/ && docker build -t nginx-example .'
}
}
}
Собираем:
Готово.



















