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

Автор: | 22/06/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:

[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]

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

Готово.