Docker Registryを使って個人プライベートコンテナリポジトリを作成する

# はじめに
基本的には、公式ドキュメント通りに進めば行けるのですが、
Basic認証でハマったので、一通り手順を公開します。

[Deploying a registry server](https://docs.docker.com/registry/deploying/)

# 環境
* Docker version 1.9.1
* docker-compose version 1.5.2

# 構成
下がファイル郡の構成となります。

```
+ registry
  + auth
    - htpasswd
  + certs
    - domain.crt
    - domain.key
  - docker-compose.yml
```

サーバ側は192.168.33.40でdocker registryを起動して、
そこに対して各サーバからイメージの送信・取得を行っていきます。

```
                          docker registry
[192.168.33.20]  <--->  [192.168.33.40]
docker push/pull
```

# オレオレ証明書作成
Docker Registryは通信の際にHTTPSを使用するので、証明書を作成します。
個人で使用するので自己証明書で代用します。

Country Nameなど入力項目がありますが、ほぼ空で大丈夫です。
Common Nameの箇所だけサーバのIPかドメインなどを入れてやります。

```sh
$ mkdir -p certs
$ openssl req \
>  -newkey rsa:4096 -nodes -sha256 -keyout certs/domain.key \
>  -x509 -days 365 -out certs/domain.crt
Generating a 4096 bit RSA private key
..........................+++
................................+++
unable to write 'random state'
writing new private key to 'domain.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:192.168.33.40
Email Address []:
```

# 証明書を作業用サーバに保存

作成した証明書を作業用サーバに保存してやります。

```
$ sudo mkdir -p /etc/docker/certs.d/192.168.33.40:5000
$ cd /etc/docker/certs.d/192.168.33.40:5000/
$ scp core@192.168.33.40:/path/to/certs/domain.crt ./ca.crt
```
これをしておかないと、bad certificate となってしまいます。

```
$ docker login 192.168.33.40:5000
Error response from daemon: invalid registry endpoint https://192.168.33.40:5000/v0/: unable to ping registry endpoint https://192.168.33.40:5000/v0/
v2 ping attempt failed with error: Get https://192.168.33.40:5000/v2/: dial tcp 192.168.33.40:5000: i/o timeout
v1 ping attempt failed with error: Get https://192.168.33.40:5000/v1/_ping: dial tcp 192.168.33.40:5000: i/o timeout. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry 192.168.33.40:5000` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/192.168.33.40:5000/ca.crt
```

# Basic認証用ファイル作成

以下のコマンドでhtpasswdファイルを作成します。

```sh
$ htpasswd -Bbn suzuki password > auth/htpasswd
```

この際オプションに注意！必ず -B オプションを付けてBcryptを利用して暗号化するようにします。
これを付けてやらないと以下の様にdocker loginコマンド実行時に認証エラーとなってしまいます。

```sh
[masa@localhost]% docker login 192.168.33.40:5000
Username: suzuki
Password: 
Email: 
Error response from daemon: no successful auth challenge for https://192.168.33.40:5000/v2/ - errors: [basic auth attempt to https://192.168.33.40:5000/v2/ realm "Registry Realm" failed with status: 401 Unauthorized]
```

# docker-compose
毎回docker runでオプション付けて起動するのは面倒なので、
docker-composeを使用してやります。
volumesの項目のパスは適宜変更してやります。

```sh:docker-compose.yml
registry:
  restart: always
  image: registry:2
  ports:
    - 5000:5000
  environment:
    REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt
    REGISTRY_HTTP_TLS_KEY: /certs/domain.key
    REGISTRY_AUTH: htpasswd
    REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
    REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
  volumes:
    - /path/to/registry/data:/var/lib/registry
    - /path/to/registry/certs:/certs
    - /path/to/registry/auth:/auth
```

# Registry起動

ではコンテナリポジトリを起動します。

```sh
[192.168.33.40]$ docker-compose up -d
```

起動したら、作業用サーバでログインしてます。

```sh
[192.168.33.20]$ docker login 192.168.33.40:5000
Username: suzuki
Password: 
Email: 
WARNING: login credentials saved in /home/suzuki/.docker/config.json
Login Succeeded
```

ログインに成功したので、イメージを送信してみます。

```sh
[192.168.33.20]$ docker pull busybox
[192.168.33.20]$ docker tag busybox 192.168.33.40:5000/test
[192.168.33.20]$ docker rmi 192.168.33.40:5000/test
[192.168.33.20]$ docker pull 192.168.33.40:5000/test
```

# ログインに失敗する場合

ここまでで docker login に失敗してしまう場合は以下をお試し下さい。

## OSレベルでの証明書をセット

以下のドキュメントを参考に各OSに設定して下さい。
ここではCentOS7での例を実行しています。

Configuring Docker Trusted Registry
https://docs.docker.com/docker-trusted-registry/configuration/

```sh
[192.168.33.20]$ cp certs/domain.crt /etc/pki/ca-trust/source/anchors/192.168.33.40.crt
[192.168.33.20]$ update-ca-trust enable
[192.168.33.20]$ update-ca-trust
[192.168.33.20]$ systemctl restart docker
```

## 証明作成時にsubjectAltNameを付ける

```sh
[192.168.33.20]$ vi /etc/pki/tls/openssl.conf
[ v3_ca ]
subjectAltName = IP:192.168.33.40

[192.168.33.20]$ openssl req \
  -newkey rsa:4096 -nodes -sha256 -keyout certs/domain.key \
  -x509 -days 365 -out certs/domain.crt
[192.168.33.20]$ cp certs/domain.crt /etc/docker/192.168.33.40:5000/ca.crt
[192.168.33.20]$ systemctl restart docker
```

作成した証明書は registry を起動するサーバにもコピーします。

# おわりに
個人用にプライベートなコンテナリポジトリが気軽に手入ります。
本格的な運用の際にはAmazon EC2 Container Registryなどを使用するのが管理が楽ですね。

# 参考
* Configuring Docker Trusted Registry<br>
https://docs.docker.com/docker-trusted-registry/configuration/
* Insecure Registry<br>
https://docs.docker.com/registry/insecure/
* Failed tls handshake. Does not contain any IP SANs<br>
http://serverfault.com/questions/611120/failed-tls-handshake-does-not-contain-any-ip-sans