Docker: private registry в AWS [China] с S3 storage

Автор: | 06/22/2017
 

Задача: поднять CI инфрастуктуру для проекта в Китае.

Будет состоять и одного EC2 инстанса с дополнительным разделом для workspaces Jenkins‘а (что-то вроде такого — Azure: Azure Resource Manager provisioning и Jenkins в Docker), и S3 корзиной для хранилища образов Docker registry сервера.

Для запуска Docker registry надо выполнить:

  1. запустить инстанс
  2. создать корзину
  3. запустить registry на localhost
  4. настроить SSL и внешний домен
  5. настроить авторизацию
  6. добавить файл настроек registry
  7. добавить compose для запуска

Запуск Jenkins будет в следующем посте, т.к. этот и так получился достаточно длинным (UPD: Docker: AWS [China] – Jenkins в Docker).

Подготовка

Т.к. работать из Киева в Китай порой нереально из-за скорости — я из Киева подключаюсь к инстансу в Китае, и уже оттуда выполняю все действия.

Устанавливаем AWS CLI:

sudo apt install awscli

Настраиваем:

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 на инстанс в Китай.

На инстансе — проверяем архитектуру:

uname -m
x86_64

На рабочей машинке — выкачиваем 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

Что бы переодпределить или добавить параметры — создаём свой файл настроек, и монтируем его в контейнер.

mkdir conf

Создаём файл:

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

Всё работает.

Готово.