Docker Registry 作業記録

Docker Registryを使ってみることにしたので、その作業記録を取った。
基本的にリンクは注釈に記述。
原則本家のドキュメント[^document]をそのままやって行く形で実施。

# 環境
- ホストはLinux(Ubuntu 18.10)(VirtualBox上で起動)
- たまにもう一つLinux(Debian 9(stretch))(VirtualBox上で起動)を使用
- Docker 18.09
- Docker Registry 17.09

# Docker Registryの概要[^document]
- オープンソースなDocker Imageの配布アプリ
- 無料でpublicなDocker Hubや有料なDocker Trusted Registryのprivateな代替

## 基本的なコマンド(いきなり使える)

Docker Registryの起動(コンテナでいきなり起動できる。コンテナ名registry/ポート5000)

```console
$ docker run -d -p 5000:5000 --name registry registry:2
```
Docker Hubからローカルにイメージ(ubuntu)を取得(Docker Registry関係ない)

```console
$ docker pull ubuntu
```

起動したDocker Registryを指すようにイメージ(ubuntu)にタグ(localhost:5000/myfirstimage)を付ける

```console
$ docker tag ubuntu localhost:5000/myfirstimage
```
起動したDocker Registryにタグ付けしたイメージ(localhost:5000/myfirstimage)をpushする

```console
$ docker push localhost:5000/myfirstimage
```
Docker Registryからイメージをpullする(今は何も起きないけど)

```console
$ docker pull localhost:5000/myfirstimage
```
registryコンテナ(Docker Registry)を停止する

```console
$ docker stop registry
```
registryコンテナを削除する(全部消えます)

```console
$ docker container rm -v registry
```
タグ付けしたイメージが残っちゃってるので削除します(追記)

```console
$ docker image rm localhost:5000/myfirstimage
```

# Docker Registryの特徴[^about_registry]
- バージョンなどでタグ付けされたイメージの配布元
- ユーザーはdocker push/docker pullなどでアクセス
- ストレージはストレージドライバにより拡張可能
- TLSとBasic認証が可能

## イメージの命名について
- registrydomain:port/foo/bar形式(タグ除く)
- 例えば公式Docker Hubのubuntuなどは、正式に書くとdocker.io/library/ubuntuとなる

## Docker Registryのユースケース
- システムで障害発生→修正してビルド→「成功したらpush」→「registryから通知」→システム配備など
- 大規模システムでのイメージ配布
- 孤立したネットワークでのイメージ配布

## 必要条件(スキル的な前提)
- Dockerに精通していること
 - push/pullを理解していること
 - cli/daemonの違いを理解していること
- 本番環境では運用スキルも必要
 - 可用性とスケーラビリティ
 - ロギングとログ処理
 - システム監視
 - セキュリティ
- 推奨
 - httpおよびネットワーク通信の深い理解
 - golangに精通

# レジストリサーバーを展開する[^Deploy a registry server]
ローカルレジストリを実行する〜ローカルレジストリを停止するまでは概要でやったので割愛。
テスト環境でない場合はTLSにして、アクセス制御しろとのお達しがあったことだけ補足。

## 基本構成
レジストリを設定するための基本的なガイドラインについて説明するが、詳しくは、レジストリー構成リファレンス[^Configuring a registry]を参照。
### レジストリを自動的に起動する
```--restart always```フラグを使用してレジストリの再起動ポリシーを設定

```console
$ docker run -d -p 5000:5000 --restart=always --name registry registry:2
```
### 公開ポートをカスタマイズする
ポートフォワード設定は以下のように簡単に変えられる(5000→5001)が、

```console
$ docker run -d -p 5001:5000 --name registry-test registry:2
```
コンテナ内のListenポートを変更する場合は、環境変数```REGISTRY_HTTP_ADDR```を使用して変更する

```console
$ docker run -d -e REGISTRY_HTTP_ADDR=0.0.0.0:5001 -p 5001:5001 --name registry-test registry:2
```
## ストレージのカスタマイズ
### 保管場所をカスタマイズする
デフォルトでは、ホストファイルシステム上のdockerボリュームとして永続化されている。
SSDやSANなどレジストリの内容をホストファイルシステムの特定の場所に格納する場合は、代わりにbind mountを使用する。
多くの場合パフォーマンスが向上する。(向上が見込めないので未実施)

```console
$ docker run -d -p 5000:5000 --restart=always --name registry -v /mnt/registry:/var/lib/registry registry:2
```
なお、ストレージドライバを用意することで、バックエンドを変更する拡張も可能。

## 外部からアクセス可能なレジストリを実行する
### 証明書を入手する
#### (1) ```certs```ディレクトリを作成

```console
$ mkdir -p certs
```
この中に証明書を入れる。
本来は然るべき証明書を入手すべきだが、【追記】しかし面倒なので自己証明書(オレオレ証明書)を作成する。[^oreore]

```console
cd certs
export URLNAME=example.com(のようなドメイン。以下example.comを仮定して記述。.は必須)
openssl req -x509 -days 36500 -newkey rsa:2048 -nodes \
-out ${URLNAME}.crt -keyout ${URLNAME}.key \
-subj "/C=JP/ST=Tokyo/L=null/O=null/OU=null/CN=${URLNAME}/"
cd ..
```
#### (2) レジストリが実行中の場合は停止

```console
$ docker stop registry
$ docker container rm -v registry ← コンテナを「ボリュームごと」削除も追記。残したい場合は-v不要。
```
#### (3) TLS証明書を使用するように指示して、レジストリを再起動
ポートも443にする。

```console
$ docker run -d \
  --restart=always \
  --name registry \
  -v `pwd`/certs:/certs \
  -e REGISTRY_HTTP_ADDR=0.0.0.0:443 \
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/example.com.crt \
  -e REGISTRY_HTTP_TLS_KEY=/certs/example.com.key \
  -p 443:443 \
  registry:2
```
#### (4) Docker Hubからpullしたイメージを、外部アドレスを使用してレジストリにpush/pull
これを実施するためには、現在DockerやDocker Registryを動かしているホスト以外のホストが必要になる。

##### (4-1)別ホストの用意(追記)
今回は実はVirtualBox上のUbuntuで行っているため、もう一つdebianのVMを立ち上げて実施した。
ネットワークは双方をVirtualBoxのNATネットワークで繋げ[^nat_network]、互いの/etc/hostsファイルにそれぞれのドメインを記述した。
具体的にはdebian側のhostsファイルにexample.comを直書きし、ubuntuのIPを指定した。
これにより、debianがexample.com指定でubuntuにアクセス可能になる(TLSのサーバ認証に必要)。

##### (4-2)自己証明書の登録(追記)
自己証明書をTLSで使用するためには、クライアント側でその証明書を登録しておく必要がある。
dockerでは、OSが提供するルート証明書も使用するが、独自にサーバ証明書を追加することができるので、今回はdocker独自の入れ物に自己証明書を登録する[^Verify repository client with certificates]。入れ先は以下のとおり。

```
/etc/docker/certs.d/example.com/example.com.crt
```

クライアント証明も可能だが、必要な場面は限られると思うので、今回は行っていない。
##### (4-3)別マシンのクライアントからpush/pull
以下はdebian上で実施する

```console
$ docker pull bash
$ docker tag bash example.com/my-bash
$ docker push example.com/my-bash
$ docker pull example.com/my-bash
```

中間証明書についてはここでは扱わない。

### Let's Encryptのサポート〜安全でないレジストリを使用する（テストのみ）→割愛

暗号化はしたいが、インターネットに公開するわけでもないのに、Let's Encyptは負担が大きいので(90日だから)。
(今のオレオレ証明書は100年設定)

## サービスとしてレジストリを実行する〜負荷分散の考慮事項

Swarmモードで使う予定がないので割愛

## アクセス制限
- ローカルネット上で動作する場合を除いて、レジストリは常にアクセス制限を実装するべき

### Basic認証
- TLS前提

#### (1) パスワードファイル作成

registryコンテナのhtpasswdを使用して、ユーザー```testuser```(変更してください)/パスワード```testpassword```(変更してください)のエントリを1つ含むパスワードファイル```auth/htpasswd```をホストに作成。
(追記:このファイルはvolumeとしてコンテナからマウントされます。イメージに入れるものではないので。)

```console
$ mkdir auth
$ docker run \
  --entrypoint htpasswd \
  registry:2 -Bbn testuser testpassword > auth/htpasswd
```

#### (2) レジストリを停止

```console
$ docker stop registry
$ docker container rm registry # 追記
```

#### (3) Basic認証でレジストリを起動

(追記:ポートは5000を443に変更しています。Realmは必要なら変更してください。)

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

#### (4) レジストリに認証なしでアクセス

レジストリからイメージを取得するか、レジストリにをプッシュする。
(追記: 以下に例を追加。debianで実行)

```
$ docker push example.com/my-bash
```

コマンドは正しく失敗する。

#### (5) レジストリにログイン

```
$ docker login example.com
$ docker push example.com/my-bash
```
コマンドは成功する。

### より高度な認証

- プロクシを使用した認証
- トークンサーバーにリダイレクトする委任認証

## Composeファイルを使用してレジストリを配置する

```Yaml:docker-compose.yml
registry:
  restart: always
  image: registry:2
  ports: # 元は5000:5000
    - 443:443
  environment:
  # 追記(必要なら適宜修正)
    REGISTRY_HTTP_ADDR: 0.0.0.0:443
    REGISTRY_HTTP_TLS_CERTIFICATE: /certs/example.com.crt
    REGISTRY_HTTP_TLS_KEY: /certs/example.com.key
    REGISTRY_AUTH: htpasswd
    REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
    REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
  volumes:
    - ./data:/var/lib/registry
    - ./certs:/certs
    - ./auth:/auth
```
```console
$ mkdir data # 追記
$ docker stop registry # 追記
$ docker container rm registry # 追記
$ # 追記: 必要ならvirtualenv切替
$ docker-compose up -d
```
debianから確認(追記)

```console
$ docker push example.com/my-bash
```

[^document]: https://docs.docker.com/v17.09/registry/
[^about_registry]: https://docs.docker.com/v17.09/registry/introduction/
[^Deploy a registry server]: https://docs.docker.com/v17.09/registry/deploying/
[^Configuring a registry]: https://docs.docker.com/v17.09/registry/configuration/
[^oreore]: [オレオレ証明書を１コマンド一発でバッチ的に作る方法](http://nopipi.hatenablog.com/entry/2018/11/11/003538)
[^Verify repository client with certificates]: https://docs.docker.com/engine/security/certificates/
[^nat_network]: [【VirtualBox】NATネットワークでゲストPC同士を通信させる](http://atomiyama.com/linux/page/natnetwork-01/)