Задача: поднять CI инфрастуктуру для проекта в Китае.
Будет состоять и одного EC2 инстанса с дополнительным разделом для workspaces
Jenkins‘а (что-то вроде такого — Azure: Azure Resource Manager provisioning и Jenkins в Docker), и S3 корзиной для хранилища образов Docker registry сервера.
Для запуска Docker registry надо выполнить:
- запустить инстанс
- создать корзину
- запустить registry на localhost
- настроить SSL и внешний домен
- настроить авторизацию
- добавить файл настроек registry
- добавить compose для запуска
Запуск Jenkins будет в следующем посте, т.к. этот и так получился достаточно длинным (UPD: Docker: AWS [China] – Jenkins в Docker).
Подготовка
Т.к. работать из Киева в Китай порой нереально из-за скорости — я из Киева подключаюсь к инстансу в Китае, и уже оттуда выполняю все действия.
Устанавливаем AWS CLI:
Настраиваем:
aws configure
AWS Access Key ID [None]: AKI***L5A
AWS Secret Access Key [None]: OtN***7Cb
Default region name [None]: cn-north-1
Default output format [None]: json
Проверяем:
aws ec2 describe-instances | head
{
"Reservations": [
{
"Groups": [],
"OwnerId": "359664176615",
"Instances": [
{
"VpcId": "vpc-34cd1f50",
"ProductCodes": [],
"VirtualizationType": "hvm",
Запускаем инстанс, в котором будут жить Jenkins и Docker registry:
aws ec2 run-instances --image-id ami-a163b4cc --key-name tag-cn --instance-type t2.micro
Задаём теги:
aws ec2 create-tags --resources i-01edb17886a20b25d --tags Key=Name,Value=tag-cn-ci-server
Проверяем:
aws ec2 describe-instances --instance-ids i-01edb17886a20b25d --query 'Reservations[*].Instances[*].[ImageId,Tags[*]]' --output text
ami-a163b4cc
Name tag-cn-ci-server
Получаем новый EIP:
aws ec2 allocate-address --domain vpc
{
"AllocationId": "eipalloc-37ca8c0d",
"Domain": "vpc",
"PublicIp": "54.***.***.251"
}
Подключаем его к EC2:
aws ec2 associate-address --public-ip 54.***.***.251 --instance-id i-01edb17886a20b25d
{
"AssociationId": "eipassoc-8a80f4b7"
}
Проверяем:
aws ec2 describe-instances --instance-ids i-01edb17886a20b25d --query 'Reservations[*].Instances[*].NetworkInterfaces[*].Association.PublicIp' --output text
54.***.***.251
Подключаемся:
ssh ubuntu@54.***.***.251 -i .ssh/tag-cn.pem
Обновляем пакеты и устанавливаем Docker:
apt-get update && apt-get upgrade && curl https://get.docker.com | bash
Ох, это же Китай… Идём пить чай. Много чая.
Добавляем пользователя в группу docker
:
usermod -aG docker ubuntu
Устанавливаем Docker Compose.
Находим последнюю версию тут>>>:
curl -L https://github.com/docker/compose/releases/download/1.14.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
На самом деле — таким образом Compose в Китае установить достаточно сложно:
curl -L https://github.com/docker/compose/releases/download/1.14.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 617 0 617 0 0 457 0 --:--:-- 0:00:01 --:--:-- 457
0 0 0 0 0 0 0 0 --:--:-- 0:02:08 --:--:-- 0curl: (7) Failed to connect to github-production-release-asset-2e65be.s3.amazonaws.com port 443: Connection timed out
Поэтому — качаем его на рабочую машину, а потом scp
на инстанс в Китай.
На инстансе — проверяем архитектуру:
На рабочей машинке — выкачиваем Compose и копируем его в Китай:
cd /tmp
curl -L https://github.com/docker/compose/releases/download/1.14.0/docker-compose-Linux-x86_64 > docker-compose
chmod +x docker-compose
scp -i /home/setevoy/Work/AKQA/LON.TAG/.ssh/tag-cn.pem docker-compose ubuntu@54.***.***.251:/tmp
И на инстансе — перемещаем исполняемый файл:
mv /tmp/docker-compose /usr/local/bin/
docker-compose
Define and run multi-container applications with Docker.
Usage:
docker-compose [-f <arg>...] [options] [COMMAND] [ARGS...]
docker-compose -h|--help
...
Установка Docker registry
S3 для registry storage
Для registry — нам потребуются S3 корзина для хранения образов и SSL сертификат. Запуск Docker registry описан тут>>>.
Создаём корзину:
aws s3api create-bucket --bucket tag-registry --region cn-north-1 --create-bucket-configuration LocationConstraint=cn-north-1
{
"Location": "http://tag-registry.s3.cn-north-1.amazonaws.com.cn/"
}
Создаём файл политик доступа:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:ListBucket",
"s3:GetBucketLocation",
"s3:ListBucketMultipartUploads"
],
"Resource": "arn:aws:s3:::tag-registry"
},
{
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListMultipartUploadParts",
"s3:AbortMultipartUpload"
],
"Resource": "arn:aws:s3:::tag-registry/*"
}
]
}
Подключаем его к корзине:
aws s3api put-bucket-policy --bucket tag-registry --policy file://tag-registry-policy.json
An error occurred (MalformedPolicy) when calling the PutBucketPolicy operation: Policy has invalid resource
Упс.
Ошибка вызвана Китаем:
«Resource»: «arn:aws:s3:::tag-registry/*»
Для китайского AWS ресурс указывается через aws-cn
:
«Resource»: «arn:aws-cn:s3:::tag-registry/*»
Обновляем, подключаем.
Запуск Docker registry
Сейчас можно проверить работу registry.
Запускаем его.
Команда выглядит так:
docker run -d -p 5000:5000 \
-e "REGISTRY_STORAGE=s3" \
-e "REGISTRY_STORAGE_S3_REGION=us-east-1"\
-e "REGISTRY_STORAGE_S3_BUCKET=******"\
-e "REGISTRY_STORAGE_S3_ACCESSKEY=******"\
-e "REGISTRY_STORAGE_S3_SECRETKEY=******"\
registry:2
Выполняем:
docker run -p 5000:5000 -e "REGISTRY_STORAGE=s3" -e "REGISTRY_STORAGE_S3_REGION=cn-north-1" -e "REGISTRY_STORAGE_S3_BUCKET=tag-registry" -e "REGISTRY_STORAGE_S3_ACCESSKEY=AKI***L5A" -e "REGISTRY_STORAGE_S3_SECRETKEY=OtN***7Cb" registry:2
Unable to find image 'registry:2' locally
2: Pulling from library/registry
...
Проверяем:
curl -X GET localhost:5000/v2/_catalog
{"errors":[{"code":"UNAVAILABLE","message":"service unavailable","detail":"health check failed: please see /debug/health"}]}
*лять.
ОК — ошибка возникает из-за пустой корзины, как говорится тут>>>.
Загружаем в неё файл и пробуем ещё раз:
aws s3 cp file s3://tag-registry
upload failed: ./file to s3://tag-registry/file seek() takes 2 positional arguments but 3 were given
Ссу*а! 🙂
Решение — тут>>>.
Загружаем:
aws s3 cp file s3://tag-registry
upload: ./file to s3://tag-registry/file
Проверяем registry:
curl -X GET localhost:5000/v2/_catalog
{"repositories":[]}
Добавляем в него образ:
docker pull ubuntu
docker tag ubuntu localhost:5000/ubuntu
docker push localhost:5000/ubuntu
The push refers to a repository [localhost:5000/ubuntu]
0566c118947e: Pushed
6f9cf951edf5: Pushed
182d2a55830d: Pushed
5a4c2c9a24fc: Pushed
cb11ba605400: Pushed
latest: digest: sha256:a0ee7647e24c8494f1cf6b94f1a3cd127f423268293c25d924fbe18fd82db5a4 size: 1357
Проверяем содержимое корзины:
aws s3 ls --recursive s3://tag-registry
2017-06-21 14:59:09 47103294 docker/registry/v2/blobs/sha256/75/75c416ea735c42a4a0b2c8f31946a1918adc7853373c411abbec424391fb989c/data
2017-06-21 14:59:12 1357 docker/registry/v2/blobs/sha256/a0/a0ee7647e24c8494f1cf6b94f1a3cd127f423268293c25d924fbe18fd82db5a4/data
...
2017-06-21 14:59:12 71 docker/registry/v2/repositories/ubuntu/_manifests/tags/latest/index/sha256/a0ee7647e24c8494f1cf6b94f1a3cd127f423268293c25d924fbe18fd82db5a4/link
SSL
Следующим шагом — требуется добавить SSL сертификат, что бы Docker registry работал через внешний домен.
Используем Let’s Encrypt.
Устанавливаем его:
git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt
Получаем сертификат:
cd /opt/letsencrypt/
./letsencrypt-auto certonly -d registry.domain.cn
...
Перезапускаем registry уже с этим сертификатом:
docker run -p 5000:5000 -v /etc/letsencrypt/archive/registry.domain.cn/:/certs -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/fullchain1.pem -e REGISTRY_HTTP_TLS_KEY=/certs/privkey1.pem -e "REGISTRY_STORAGE=s3" -e "REGISTRY_STORAGE_S3_REGION=cn-north-1" -e "REGISTRY_STORAGE_S3_BUCKET=tag-registry" -e "REGISTRY_STORAGE_S3_ACCESSKEY=AKI***L5A" -e "REGISY_STORAGE_S3_SECRETKEY=OtN***7Cb" registry:2
Тегаем, пушим образ Ubuntu в репозиторий:
docker tag ubuntu registry.domain.cn:5000/ubuntu
docker push registry.domain.cn:5000/ubuntu
The push refers to a repository [registry.domain.cn:5000/ubuntu]
0566c118947e: Retrying in 5 seconds
6f9cf951edf5: Retrying in 5 seconds
...
Так…
Логи:
time=»2017-06-21T16:00:45Z» level=error msg=»response completed with error» err.code=unknown err.detail=»s3aws: NoCredentialProviders: no valid providers in chain. Deprecated. \n\tFor verbose messaging see aws.Config.CredentialsChainVerboseErrors»
curl
?
curl -X GET https://registry.domain.cn:5000/v2/_catalog
{"errors":[{"code":"UNAVAILABLE","message":"service unavailable","detail":"health check failed: please see /debug/health"}]}
«NoCredentialProviders» — ОК, пробуем добавить данные доступа AWS:
docker run -p 5000:5000 -v /etc/letsencrypt/archive/registry.domain.cn/:/certs -e AWS_ACCESS_KEY_ID=AKI***L5A -e AWS_SECRET_ACCESS_KEY=OtN***7Cb -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/fullchain1.pem -e REGISTRY_HTTP_TLS_KEY=/certs/privkey1.pem -e "REGISTRY_STORAGE=s3" -e "REGISTRY_STORAGE_S3_REGION=cn-north-1" -e "REGISTRY_STORAGE_S3_BUCKET=registry.domain.cn" -e "REGISTRY_STORAGE_S3_ACCESSKEY=AKI***L5A" -e "REGISY_STORAGE_S3_SECRETKEY=OtN***7Cb" registry:2
Проверяем:
docker push registry.domain.cn:5000/ubuntu
The push refers to a repository [registry.domain.cn:5000/ubuntu]
0566c118947e: Pushed
6f9cf951edf5: Pushed
182d2a55830d: Pushed
5a4c2c9a24fc: Pushed
cb11ba605400: Pushed
latest: digest: sha256:a0ee7647e24c8494f1cf6b94f1a3cd127f423268293c25d924fbe18fd82db5a4 size: 1357
Проверяем:
curl -X GET https://registry.domain:5000/v2/_catalog
{"repositories":["ubuntu"]}
Работает, отлично.
HTTP авторизация
Останавливаем registry, создаём каталог для файла htpasswd
и сам файл с пользователем и паролем:
mkdir auth
docker run --entrypoint htpasswd registry -Bbn dockeruser dockerpass > auth/htpasswd
Unable to find image 'registry:latest' locally
latest: Pulling from library/registry
Digest: sha256:a3551c422521617e86927c3ff57e05edf086f1648f4d8524633216ca363d06c2
Status: Downloaded newer image for registry:latest
Запускаем, добавив переменные REGISTRY_AUTH
, REGISTRY_AUTH_HTPASSWD_REALM
и REGISTRY_AUTH_HTPASSWD_PATH
, монтируем папку auth
в контейнер:
docker run -p 5000:5000 -v /etc/letsencrypt/archive/registry.domain.cn/:/certs -e AWS_ACCESS_KEY_ID=AKI***L5A -e AWS_SECRET_ACCESS_KEY=OtN***7Cb -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/fullchain1.pem -e REGISTRY_HTTP_TLS_KEY=/certs/privkey1.pem -e "REGISTRY_STORAGE=s3" -e "REGISTRY_STORAGE_S3_REGION=cn-north-1" -e "REGISTRY_STORAGE_S3_BUCKET=tag-registry" -e "REGISTRY_STORAGE_S3_ACCESSKEY=AKI***L5A" -e "REGISY_STORAGE_S3_SECRETKEY=OtN***7Cb" -v /home/ubuntu/auth:/auth -e "REGISTRY_AUTH=htpasswd" -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd registry:2
Пробуем push
без авторизации:
docker push registry.domain.cn:5000/ubuntu
The push refers to a repository [registry.domain.cn:5000/ubuntu]
0566c118947e: Preparing
6f9cf951edf5: Preparing
182d2a55830d: Preparing
5a4c2c9a24fc: Preparing
cb11ba605400: Preparing
no basic auth credentials
Ошибка в логе выглядит так:
54.***.***.251 — — [22/Jun/2017:08:50:29 +0000] «GET /v2/ HTTP/1.1» 401 87 «» «docker/17.05.0-ce go/go1.7.5 git-commit/89658be kernel/4.4.0-64-generic os/linux arch/amd64 UpstreamClient(Docker-Client/17.05.0-ce \\(linux\\))»
time=»2017-06-22T08:51:26Z» level=warning msg=»error authorizing context: basic authentication challenge for realm \»Registry Realm\»: invalid authorization credential»
Авторизируемся:
docker login registry.domain.cn:5000
Username: dockeruser
Password:
Login Succeeded
Пушим:
docker push registry.domain.cn:5000/ubuntu
The push refers to a repository [registry.domain.cn:5000/ubuntu]
0566c118947e: Layer already exists
6f9cf951edf5: Layer already exists
182d2a55830d: Layer already exists
5a4c2c9a24fc: Layer already exists
cb11ba605400: Layer already exists
latest: digest: sha256:a0ee7647e24c8494f1cf6b94f1a3cd127f423268293c25d924fbe18fd82db5a4 size: 1357
Готово.
Docker registry config.yml
Часть параметров, которые сейчас указываются аргументами к docker run
будут в docker-compose.yml
файле, часть — в файле config.yml
.
config.yml
есть в контейнере по умолчанию:
docker run registry:2 cat /etc/docker/registry/config.yml
version: 0.1
log:
fields:
service: registry
storage:
cache:
blobdescriptor: inmemory
filesystem:
rootdirectory: /var/lib/registry
http:
addr: :5000
headers:
X-Content-Type-Options: [nosniff]
health:
storagedriver:
enabled: true
interval: 10s
threshold: 3
Что бы переодпределить или добавить параметры — создаём свой файл настроек, и монтируем его в контейнер.
Создаём файл:
version: 0.1
log:
fields:
service: registry
storage:
s3:
accesskey: AKI***L5A
secretkey: OtN***7Cb
region: cn-north-1
bucket: tag-registry
http:
addr: :5000
headers:
X-Content-Type-Options: [nosniff]
health:
storagedriver:
enabled: true
interval: 10s
threshold: 3
Запускаем, убрав переменные для storage
(который уже определён в config.yml
):
docker run -p 5000:5000 -v /etc/letsencrypt/archive/registry.domain.cn/:/certs -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/fullchain1.pem -e REGISTRY_HTTP_TLS_KEY=/certs/privkey1.pem -v /home/ubuntu/auth:/auth -e "REGISTRY_AUTH=htpasswd" -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd -v /home/ubuntu/conf/config.yml:/etc/docker/registry/config.yml registry:2
Проверяем:
docker pull registry.domain.cn:5000/ubuntu
Using default tag: latest
latest: Pulling from ubuntu
Digest: sha256:a0ee7647e24c8494f1cf6b94f1a3cd127f423268293c25d924fbe18fd82db5a4
Status: Image is up to date for registry.domain.cn:5000/ubuntu:latest
Работает.
Больше про конфигурацию — тут>>>. Надо будет ещё добавить очистку корзины, что-то вроде такого>>>.
Docker compose для Docker registry
Создаём файл, в котором указываем все каталоги для монтирования в контейнер и переменные для SSL:
registry:
restart: always
image: registry:2
ports:
- 5000:5000
environment:
REGISTRY_HTTP_TLS_CERTIFICATE: /certs/fullchain1.pem
REGISTRY_HTTP_TLS_KEY: /certs/privkey1.pem
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
volumes:
- /etc/letsencrypt/archive/registry.domain.cn:/certs
- /home/ubuntu/auth:/auth
- /home/ubuntu/conf/config.yml:/etc/docker/registry/config.yml
Запускаем:
docker-compose up
Starting ubuntu_registry_1 ...
Starting ubuntu_registry_1 ... done
Attaching to ubuntu_registry_1
registry_1 | time="2017-06-22T09:20:48Z" level=warning msg="No HTTP secret provided - generated random secret. This may cause problems with uploads if multiple registries are behind a load-balancer. To provide a shared secret, fill in http.secret in the configuration file or set the REGISTRY_HTTP_SECRET environment variable." go.version=go1.7.3 instance.id=cd4f52be-565b-4261-b19f-df67edb5da3a version=v2.6.1
registry_1 | time="2017-06-22T09:20:48Z" level=info msg="redis not configured" go.version=go1.7.3 instance.id=cd4f52be-565b-4261-b19f-df67edb5da3a version=v2.6.1
registry_1 | time="2017-06-22T09:20:48Z" level=info msg="Starting upload purge in 50m0s" go.version=go1.7.3 instance.id=cd4f52be-565b-4261-b19f-df67edb5da3a version=v2.6.1
registry_1 | time="2017-06-22T09:20:48Z" level=info msg="listening on [::]:5000, tls" go.version=go1.7.3 instance.id=cd4f52be-565b-4261-b19f-df67edb5da3a version=v2.6.1
...
Проверяем:
docker pull registry.domain.cn:5000/ubuntu
Using default tag: latest
latest: Pulling from ubuntu
Digest: sha256:a0ee7647e24c8494f1cf6b94f1a3cd127f423268293c25d924fbe18fd82db5a4
Status: Image is up to date for registry.domain.cn:5000/ubuntu:latest
Пуш:
docker push registry.domain.cn:5000/ubuntu
The push refers to a repository [registry.domain.cn:5000/ubuntu]
0566c118947e: Layer already exists
6f9cf951edf5: Layer already exists
182d2a55830d: Layer already exists
5a4c2c9a24fc: Layer already exists
cb11ba605400: Layer already exists
latest: digest: sha256:a0ee7647e24c8494f1cf6b94f1a3cd127f423268293c25d924fbe18fd82db5a4 size: 1357
Всё работает.
Готово.