Docker Registryの簡易セットアップ

ちょっとした検証などのために、イントラネットなどにプライベートなDocker Registryを構築する手順をまとめました。
自己証明書によるTLS、BASIC認証によるアクセス制限、CraneOperatorによるWeb UI付きです。

もっと手軽にRegistryを準備するにはクラウドで提供されているサービスを利用した方が楽ですね。Oracle Cloud Infrastructure Registry (OCIR)を使う手順を[別の記事](https://qiita.com/shakiyam/items/ff36c27b31b42835263e)にまとめました。(2019/1/15 追記)


TL;DR (要約)
------------

手順をスクリプト化して https://github.com/shakiyam/registry にアップしています。

要件
----

Docker EngineとDocker Composeが必要です。

Oracle Linux 7.6とUbuntu 16.04で確認済みです。Oracle LinuxはRHEL互換なので、CentOS 7等でも動作するでしょう。

手順1. ローカル実行
-------------------

Docker Hubで公開されている[Docker Registryイメージ](https://hub.docker.com/_/registry/)を使って、ローカル環境でレジストリを起動します。

```console
docker container run -d -p 5000:5000 --restart=always --name registry registry:2
```

これでレジストリが使えるようになりました。

Docker Hubからイメージを手に入れ、自分のレジストリ上にタグ付けします。

```console
docker image pull hello-world:latest
docker image tag hello-world:latest localhost:5000/hello-world:latest
```

タグ付けしたイメージをpushします。

```console
docker push localhost:5000/hello-world:latest
```

curlでAPIを呼び出し、hello-worldが登録されていることを確認します。

```console
$ curl http://localhost:5000/v2/_catalog
{"repositories":["hello-world"]}
```

レジストリを停止するには、次のようにします。

```console
docker container stop registry && docker container rm -v registry
```

手順2. 自己証明書によるTLS
--------------------------

他のホストから接続できるように、TLSを使用できるようにします。

### サーバー側の作業 ###

まず、自己証明書を作成します。
Country Nameなど入力項目がありますが、Common Name以外は空で問題ありません。
Common Nameにはホスト名(下記の例では registry.example.com)を入れます。

```console
$ mkdir -p certs
$ openssl req -newkey rsa:2048 -nodes -sha256 -keyout certs/ca.key -x509 -days 3650 -out certs/ca.crt
Generating a 2048 bit RSA private key
.....................+++
.........................+++
writing new private key to 'certs/ca.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) []:registry.example.com
Email Address []:
```

次に、作成された証明書を指定してレジストリを起動します。

```console
docker container run -d \
  -p 443:443 \
  --restart=always \
  --name registry \
  -v $(pwd)/certs:/certs \
  -e REGISTRY_HTTP_ADDR=0.0.0.0:443 \
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/ca.crt \
  -e REGISTRY_HTTP_TLS_KEY=/certs/ca.key \
  registry:2
```

### クライアント側の作業 ###

サーバーで作成した証明書をクライアントに追加します。Oracle Linux 7とUbuntuとで手順が異なります。

Oracle Linux 7の場合は、/usr/share/pki/ca-trust-source/anchorsに証明書を配置し、update-ca-trustを実行します。

```console
sudo scp registry@registry.example.com:/home/registry/certs/ca.crt /etc/pki/ca-trust/source/anchors/registry.example.com.crt
sudo update-ca-trust
```

Ubuntuの場合は、/usr/share/ca-certificatesに証明書を配置し、/etc/ca-certificates.confに/usr/share/ce-certificetsからの相対パスでファイル名を追記してから、update-ca-trustを実行します。

```console
sudo scp registry@registry.example.com:/home/registry/certs/ca.crt /usr/share/ca-certificates/registry.example.com.crt
echo registry.example.com.crt | sudo tee -a /etc/ca-certificates.conf >/dev/null
sudo update-ca-certificates
```

証明書の追加が終わったら、Docker Engineを再起動します。

```
sudo systemctl restart docker
```

手順1と同様にイメージにタグ付を行い、pushと確認を行います。

```console
docker image tag hello-world:latest registry.example.com/hello-world:latest
docker push registry.example.com/hello-world:latest
curl https://registry.example.com/v2/_catalog
```

手順3. BASIC認証によるアクセス制限
----------------------------------

このままでは誰でもアクセスできてしまいますので、BASIC認証によるアクセス制限を設定します。

### サーバー側の作業 ###

まず、htpasswdコマンドを使ってユーザにパスワードを発行します。htpasswdコマンドはDocker Registryイメージに含まれているので、これを利用します。下記の例では、ユーザー名がusername、パスワードがpasswordとなっています。

```console
mkdir -p auth
docker run \
  --rm \
  --entrypoint htpasswd \
  registry:2 \
  -nb -B username password > auth/htpasswd
```

次に、作成されたhtpasswdを指定してレジストリを起動します。

```console
docker container run -d \
  -p 443:443 \
  --restart=always \
  --name registry \
  -v $(pwd)/auth:/auth \
  -v $(pwd)/certs:/certs \
  -v REGISTRY_AUTH=htpasswd \
  -v REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \
  -v REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
  -e REGISTRY_HTTP_ADDR=0.0.0.0:443 \
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/ca.crt \
  -e REGISTRY_HTTP_TLS_KEY=/certs/ca.key \
  registry:2
```

### クライアント側の作業 ###

docker loginでログインします。

```console
echo password | docker login registry.example.com --password-stdin -u username
```

手順2と同様にpushと確認を行います。

```console
docker push registry.example.com/hello-world:latest
curl https://registry.example.com/v2/_catalog
```

手順4. CraneOperatorによるWeb UI
--------------------------------

curlでAPIを呼びだすのも大変なので、[CraneOperator](https://hub.docker.com/r/parabuzzle/craneoperator)によるWeb UIを構築します。

### サーバー側の作業 ###

Docker RegistryとCraneOperatorの2つのコンテナを起動しますので、Dokcer Composeを使用します。

下記のようなdocker-compose.ymlを用意します。
この例では、Docker RegistryとCraneOperatorの設定でイメージの削除を有効化しています。
また、レジストリのデータを格納するdocker volumeにregistryという名前を付けています。
なお、REGISTRY_HOSTはレジストリのコンテナを指していますので、registry.example.comではなくregistryを指定しています。

```yaml:docker-compose.yml
version: '3'
services:
  registry:
    image: registry:2
    volumes:
      - ./auth:/auth
      - ./certs:/certs
      - registry:/var/lib/registry
    ports:
      - "443:443"
    environment:
      - REGISTRY_AUTH=htpasswd
      - REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm"
      - REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd
      - REGISTRY_HTTP_ADDR=0.0.0.0:443
      - REGISTRY_HTTP_TLS_CERTIFICATE=/certs/ca.crt
      - REGISTRY_HTTP_TLS_KEY=/certs/ca.key
      - REGISTRY_STORAGE_DELETE_ENABLED=true
    restart: always
  web:
    depends_on:
      - registry
    image: parabuzzle/craneoperator:latest
    ports:
      - "8000:80"
    environment:
      - REGISTRY_HOST=registry
      - REGISTRY_PORT=443
      - REGISTRY_PROTOCOL=https
      - SSL_VERIFY=false
      - REGISTRY_PUBLIC_URL=registry.example.com
      - REGISTRY_USERNAME=username
      - REGISTRY_PASSWORD=password
      - USERNAME=username
      - PASSWORD=password
      - REGISTRY_ALLOW_DELETE=true
    restart: always
volumes:
  registry:
```

docker-compose.ymlが用意できたのでレジストリとWeb UIを起動します。

```console
docker-compose up -d
```

### クライアント側の作業 ###

手順3と同様にpushします。

```console
docker push registry.example.com/hello-world:latest
```

ブラウザで http://registry.example.com:8000/ にアクセスします。

![crane_operator.png](https://qiita-image-store.s3.amazonaws.com/0/174/f344d5d6-87b2-a798-bb1b-c07675fadb7e.png)