Задача: поднять 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:
[simterm]
$ sudo apt install awscli
[/simterm]
Настраиваем:
[simterm]
$ 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
[/simterm]
Проверяем:
[simterm]
$ aws ec2 describe-instances | head { "Reservations": [ { "Groups": [], "OwnerId": "359664176615", "Instances": [ { "VpcId": "vpc-34cd1f50", "ProductCodes": [], "VirtualizationType": "hvm",
[/simterm]
Запускаем инстанс, в котором будут жить Jenkins и Docker registry:
[simterm]
$ aws ec2 run-instances --image-id ami-a163b4cc --key-name tag-cn --instance-type t2.micro
[/simterm]
Задаём теги:
[simterm]
$ aws ec2 create-tags --resources i-01edb17886a20b25d --tags Key=Name,Value=tag-cn-ci-server
[/simterm]
Проверяем:
[simterm]
$ aws ec2 describe-instances --instance-ids i-01edb17886a20b25d --query 'Reservations[*].Instances[*].[ImageId,Tags[*]]' --output text ami-a163b4cc Name tag-cn-ci-server
[/simterm]
Получаем новый EIP:
[simterm]
$ aws ec2 allocate-address --domain vpc { "AllocationId": "eipalloc-37ca8c0d", "Domain": "vpc", "PublicIp": "54.***.***.251" }
[/simterm]
Подключаем его к EC2:
[simterm]
$ aws ec2 associate-address --public-ip 54.***.***.251 --instance-id i-01edb17886a20b25d { "AssociationId": "eipassoc-8a80f4b7" }
[/simterm]
Проверяем:
[simterm]
$ aws ec2 describe-instances --instance-ids i-01edb17886a20b25d --query 'Reservations[*].Instances[*].NetworkInterfaces[*].Association.PublicIp' --output text 54.***.***.251
[/simterm]
Подключаемся:
[simterm]
$ ssh ubuntu@54.***.***.251 -i .ssh/tag-cn.pem
[/simterm]
Обновляем пакеты и устанавливаем Docker:
[simterm]
# apt-get update && apt-get upgrade && curl https://get.docker.com | bash
[/simterm]
Ох, это же Китай… Идём пить чай. Много чая.
Добавляем пользователя в группу docker
:
[simterm]
# usermod -aG docker ubuntu
[/simterm]
Устанавливаем Docker Compose.
Находим последнюю версию тут>>>:
[simterm]
# curl -L https://github.com/docker/compose/releases/download/1.14.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
[/simterm]
На самом деле – таким образом Compose в Китае установить достаточно сложно:
[simterm]
# 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
[/simterm]
Поэтому – качаем его на рабочую машину, а потом scp
на инстанс в Китай.
На инстансе – проверяем архитектуру:
[simterm]
# uname -m x86_64
[/simterm]
На рабочей машинке – выкачиваем Compose и копируем его в Китай:
[simterm]
$ 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
[/simterm]
И на инстансе – перемещаем исполняемый файл:
[simterm]
# 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 ...
[/simterm]
Установка Docker registry
S3 для registry storage
Для registry – нам потребуются S3 корзина для хранения образов и SSL сертификат. Запуск Docker registry описан тут>>>.
Создаём корзину:
[simterm]
$ 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/" }
[/simterm]
Создаём файл политик доступа:
{ "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/*" } ] }
Подключаем его к корзине:
[simterm]
$ 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
[/simterm]
Упс.
Ошибка вызвана Китаем:
“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
Выполняем:
[simterm]
$ 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 ...
[/simterm]
Проверяем:
[simterm]
$ curl -X GET localhost:5000/v2/_catalog {"errors":[{"code":"UNAVAILABLE","message":"service unavailable","detail":"health check failed: please see /debug/health"}]}
[/simterm]
*лять.
ОК – ошибка возникает из-за пустой корзины, как говорится тут>>>.
Загружаем в неё файл и пробуем ещё раз:
[simterm]
$ aws s3 cp file s3://tag-registry upload failed: ./file to s3://tag-registry/file seek() takes 2 positional arguments but 3 were given
[/simterm]
Ссу*а! 🙂
Решение – тут>>>.
Загружаем:
[simterm]
# aws s3 cp file s3://tag-registry upload: ./file to s3://tag-registry/file
[/simterm]
Проверяем registry:
[simterm]
$ curl -X GET localhost:5000/v2/_catalog {"repositories":[]}
[/simterm]
Добавляем в него образ:
[simterm]
$ 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
[/simterm]
Проверяем содержимое корзины:
[simterm]
# 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
[/simterm]
SSL
Следующим шагом – требуется добавить SSL сертификат, что бы Docker registry работал через внешний домен.
Используем Let’s Encrypt.
Устанавливаем его:
[simterm]
# git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt
[/simterm]
Получаем сертификат:
[simterm]
# cd /opt/letsencrypt/ # ./letsencrypt-auto certonly -d registry.domain.cn ...
[/simterm]
Перезапускаем registry уже с этим сертификатом:
[simterm]
$ 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
[/simterm]
Тегаем, пушим образ Ubuntu в репозиторий:
[simterm]
$ 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 ...
[/simterm]
Так…
Логи:
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
?
[simterm]
$ curl -X GET https://registry.domain.cn:5000/v2/_catalog {"errors":[{"code":"UNAVAILABLE","message":"service unavailable","detail":"health check failed: please see /debug/health"}]}
[/simterm]
“NoCredentialProviders” – ОК, пробуем добавить данные доступа AWS:
[simterm]
$ 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
[/simterm]
Проверяем:
[simterm]
$ 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
[/simterm]
Проверяем:
[simterm]
$ curl -X GET https://registry.domain:5000/v2/_catalog {"repositories":["ubuntu"]}
[/simterm]
Работает, отлично.
HTTP авторизация
Останавливаем registry, создаём каталог для файла htpasswd
и сам файл с пользователем и паролем:
[simterm]
$ 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
[/simterm]
Запускаем, добавив переменные REGISTRY_AUTH
, REGISTRY_AUTH_HTPASSWD_REALM
и REGISTRY_AUTH_HTPASSWD_PATH
, монтируем папку auth
в контейнер:
[simterm]
# 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
[/simterm]
Пробуем push
без авторизации:
[simterm]
$ 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
[/simterm]
Ошибка в логе выглядит так:
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”
Авторизируемся:
[simterm]
$ docker login registry.domain.cn:5000 Username: dockeruser Password: Login Succeeded
[/simterm]
Пушим:
[simterm]
$ 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
[/simterm]
Готово.
Docker registry config.yml
Часть параметров, которые сейчас указываются аргументами к docker run
будут в docker-compose.yml
файле, часть – в файле config.yml
.
config.yml
есть в контейнере по умолчанию:
[simterm]
$ 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
[/simterm]
Что бы переодпределить или добавить параметры – создаём свой файл настроек, и монтируем его в контейнер.
[simterm]
$ mkdir conf
[/simterm]
Создаём файл:
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
):
[simterm]
$ 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
[/simterm]
Проверяем:
[simterm]
$ 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
[/simterm]
Работает.
Больше про конфигурацию – тут>>>. Надо будет ещё добавить очистку корзины, что-то вроде такого>>>.
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
Запускаем:
[simterm]
$ 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 ...
[/simterm]
Проверяем:
[simterm]
$ 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
[/simterm]
Пуш:
[simterm]
$ 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
[/simterm]
Всё работает.
Готово.