Docker Container Registry サーバを自前で作成してみる

Bash,Docker,GCP,Kubernetes

はじめに

"Docker Hub"GCRECR といったサービスがあるなか、
わざわざ自前で "Docker Registry" を構築して Docker イメージを管理する必要性は少ないとは思いますが、仕組みを理解する目的で、自作の Docker Registry 環境を構築してみたいと思います。

検証環境

$ uname -moi
x86_64 MacBookPro16,1 Darwin

$ bash -version | head -n 1
GNU bash, version 5.1.8(1)-release (x86_64-apple-darwin20.3.0)

作業の流れ(作業ログ)

  1. Docker Registry サーバを立ち上げ
  2. Docker Registry サーバに外部から接続するための設定を追加

準備

前提として、今回は最もかんたんな方法で Docker Registry サーバを立ち上げたいと思います。

その方法とは、 「すでに公開されている Docker Registry サーバ用の Docker イメージを利用する」というものです。

Docker イメージを管理するためのサーバを Docker で立ち上げる、というのが面白いですね。

そんなわけで、Docker を事前にインストールしておきましょう。
Windows や Mac で動作させる最もかんたんな方法は以下のツールを導入することです。

1. Docker Registry サーバを立ち上げ

registry:2.7.1 というイメージを利用したいと思います。

起動は以下のコマンドを実行するだけです。

# 起動
docker run -d -p 5000:5000 --name registry registry:2.7.1

# 起動していることを確認
$ docker ps
CONTAINER ID   IMAGE            COMMAND                  CREATED         STATUS         PORTS                    NAMES
b7265c669cdf   registry:2.7.1   "/entrypoint.sh /etc…"   5 seconds ago   Up 4 seconds   0.0.0.0:5000->5000/tcp   registry

# ログを確認
$ docker logs -f registry
time="2021-06-12T22:57:47.5518653Z" level=info msg="Starting upload purge in 51m0s" go.version=go1.11.2 instance.id=5f4fb714-45bf-4149-ada7-17206f19591c service=registry version=v2.7.1
time="2021-06-12T22:57:47.5517737Z" 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.11.2 instance.id=5f4fb714-45bf-4149-ada7-17206f19591c service=registry version=v2.7.1
time="2021-06-12T22:57:47.5520435Z" level=info msg="redis not configured" go.version=go1.11.2 instance.id=5f4fb714-45bf-4149-ada7-17206f19591c service=registry version=v2.7.1
time="2021-06-12T22:57:47.5586291Z" level=info msg="using inmemory blob descriptor cache" go.version=go1.11.2 instance.id=5f4fb714-45bf-4149-ada7-17206f19591c service=registry version=v2.7.1
time="2021-06-12T22:57:47.5588214Z" level=info msg="listening on [::]:5000" go.version=go1.11.2 instance.id=5f4fb714-45bf-4149-ada7-17206f19591c service=registry version=v2.7.1

起動しているようです。

それでは実際に Docker イメージをビルドして、Registry に登録してみましょう。

動作確認 1 : Docker イメージのビルド

# Dockerfileの作成
$ cat <<EOF >Dockerfile
FROM alpine:3.11.3

LABEL maintainer "genzouw <genzouw@gmail.com>"

ENTRYPOINT ["echo", "Hello World"]
EOF

$ docker build . -t genzouw/hello:0.1
[+] Building 0.1s (5/5) FINISHED
 => [internal] load build definition from Dockerfile                                                                                                                                                                                                                                                                     0.0s
 => => transferring dockerfile: 143B                                                                                                                                                                                                                                                                                     0.0s
 => [internal] load .dockerignore                                                                                                                                                                                                                                                                                        0.0s
 => => transferring context: 2B                                                                                                                                                                                                                                                                                          0.0s
 => [internal] load metadata for docker.io/library/alpine:3.11.3                                                                                                                                                                                                                                                         0.0s
 => [1/1] FROM docker.io/library/alpine:3.11.3                                                                                                                                                                                                                                                                           0.0s
 => exporting to image                                                                                                                                                                                                                                                                                                   0.0s
 => => exporting layers                                                                                                                                                                                                                                                                                                  0.0s
 => => writing image sha256:9181994605bbf31cd3a13943700af4f0c40aed2bc9171185b86048025a109d10                                                                                                                                                                                                                             0.0s
 => => naming to docker.io/genzouw/hello:0.1                                                                                                                                                                                                                                                                             0.0s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

ビルドが完了したら実行してみましょう。
"Hello World" という文言が出力されるはずです。

$ docker run genzouw/hello:0.1
Hello World

動作確認 2 : Docker イメージのプッシュ

それでは先程立ち上げたローカル PC 内の Docker Registry サーバにプッシュしてみます。

# 一旦、ホスト名が付与されたイメージ名にしてやる必要があります
$ docker tag genzouw/hello:0.1 localhost:5000/genzouw/hello:0.1

$ docker push localhost:5000/genzouw/hello:0.1
The push refers to repository [localhost:5000/genzouw/hello]
5216338b40a7: Pushed
0.1: digest: sha256:fa53d95510634d654c8e71dd94b69fb6285006201a6cd8753ba78527489b72d2 size: 528

動作確認 3 : ローカルの Docker イメージを削除しても動作するか確認

それでは、登録したばかりの Docker イメージが pull して利用できるか、確認してみます。

まずはローカルに残っている Docker イメージを消してやります。

# イメージが残っていることを確認
$ docker images | grep genzouw/hello
genzouw/hello                                                                                      0.1                                                     9181994605bb   17 months ago   5.59MB
localhost:5000/genzouw/hello                                                                       0.1                                                     9181994605bb   17 months ago   5.59MB

# イメージを削除
$ docker rmi genzouw/hello:0.1
Untagged: genzouw/hello:0.1

# イメージを削除
$ docker rmi localhost:5000/genzouw/hello:0.1 --force
Untagged: localhost:5000/genzouw/hello:0.1
Untagged: localhost:5000/genzouw/hello@sha256:fa53d95510634d654c8e71dd94b69fb6285006201a6cd8753ba78527489b72d2
Deleted: sha256:9181994605bbf31cd3a13943700af4f0c40aed2bc9171185b86048025a109d10

# イメージが残っていないことを確認
$ docker images | grep genzouw/hello

それでは、Docker Registry サーバから取得してみます。

$ docker pull localhost:5000/genzouw/hello:0.1
0.1: Pulling from genzouw/hello
c9b1b535fdd9: Already exists
Digest: sha256:fa53d95510634d654c8e71dd94b69fb6285006201a6cd8753ba78527489b72d2
Status: Downloaded newer image for localhost:5000/genzouw/hello:0.1
localhost:5000/genzouw/hello:0.1

# 実行!
$ docker run localhost:5000/genzouw/hello:0.1
Hello World

登録されているものが取得できました。

2. Docker Registry サーバに外部から接続するための設定を追加

ここまで実施してきた操作はすべて、 自分の PC 内に限定 されていました。

Docker イメージの push 先ホストが localhost となっていたことからもわかると思います。

ただ、Docker Registry を使う場合はチームでの開発が想定されるでしょう。

そこで、試しに localhost127.0.0.1 といった自分の PC を示すアドレスを利用せず、 PC に割り当てられているネットワーク外からアクセス可能な IP アドレスを使って Docker Registry と通信してみます。

# 確認すると、ネットワークの他PCからは 192.168.1.14 として見える
$ ifconfig | grep 'inet '
        inet 127.0.0.1 netmask 0xff000000
        inet 192.168.1.14 netmask 0xffffff00 broadcast 192.168.1.255

# 先程のイメージに新たにIPを指定したタグを付け直します
$ docker tag localhost:5000/genzouw/hello:0.1 192.168.1.14:5000/genzouw/hello:0.1

# イメージをpushしてみます
$ docker push 192.168.1.14:5000/genzouw/hello:0.1
The push refers to repository [192.168.1.14:5000/genzouw/hello]
Get https://192.168.1.14:5000/v2/: http: server gave HTTP response to HTTPS client

# 試しにpullしてみます
$ docker pull 192.168.1.14:5000/genzouw/hello:0.1
Error response from daemon: Get https://192.168.1.14:5000/v2/: http: server gave HTTP response to HTTPS client

エラーが発生しました。

Docker Registry と Docker が外部ネットワークを使って通信するためには、 TLS 証明書を使った HTTPS 通信の仕組みが必要 なためです。

(Docker Registry 起動時のオプションを渡して、非 TLS 通信を許容することもできますがインターネット経由の場合はおすすめできません。。)

外部から Docker Registry にアクセスするための設定

TLS 証明書を作成する方法はいくつかあると思いますが、ここでは無料で利用できる 自己署名証明書 を作成します。

ここで一点、注意が必要です。Docker Registry とは IP ではなくホスト名を指定する必要があります。(IP 指定だとエラーが発生する)

今回は genzouw という名前で Docker Registry を起動したいと思います。証明書も genzouw という Common Name で作成します。

# certs/ というディレクトリを作成し、秘密鍵と証明書を作成してみます。
$ mkdir certs/

# 秘密鍵の作成
$ openssl genrsa -out certs/genzouw.key 4096

# 証明書の作成
$ openssl req -new -x509 -text -key certs/genzouw.key -out certs/genzouw.crt

秘密鍵証明書 の2つのファイルが作成されました。

自己署名証明書 を利用する場合は、これを Docker Registry からイメージを取得したい全ての PC 環境に配置する必要があります。
( 自己署名証明書ではない TLS の場合には、この作業は不要です。 )

証明書は、PC 環境の /etc/docker/certs.d/<DOCKER_REGISTRY_IP>:<DOCKER_REGISTRY_PORT>/ca.crt というファイルパスに配置します。

# ディレクトリを作成
$ mkdir -p ~/.docker/certs.d/genzouw:5000/

# 証明書をコピー
$ cp certs/genzouw.crt ~/.docker/certs.d/genzouw:5000/ca.crt

# もう一点大事なことが。 genzouw というホスト名を追加して通信できるようにしておきましょう
$ sudo vim /etc/hosts

# 反映されたかを確認
$ grep genzouw /etc/hosts
192.168.1.14 genzouw

また、証明書を配置したあとは PC 環境の Docker を再起動する必要があります。
Docker Desktop for Windows / Mac を利用している場合は以下のイメージを参照ください。

Docker Registry を利用する側の設定は完了。

Docker Registry 側の設定を行います。
すでに立ち上がっているコンテナを一度削除し、再び起動し直します。

起動の際には、 秘密鍵証明書 を利用するため、ディレクトリをマウントしてやります。

$ docker rm -v -f registry

$ docker run -d -p 5000:5000 --name registry \
 -v $PWD/certs:/certs \
 -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/genzouw.crt \
 -e REGISTRY_HTTP_TLS_KEY=/certs/genzouw.key \
 registry:2.7.1

動作確認

さて、準備は整いました。動作を確認してみます。

先程作成した Docker イメージに改めて、新しいホスト名でタグを付与します。

$ docker tag localhost:5000/genzouw/hello:0.1 genzouw:5000/genzouw/hello:0.1

Docker Registry に Push してみます。

$ docker push genzouw:5000/genzouw/hello:0.1
The push refers to repository [genzouw:5000/genzouw/hello]
Get https://genzouw:5000/v2/: x509: certificate signed by unknown authority

ローカルのイメージを削除した後、Docker Registry から Pull してみます。

# PC内のイメージを一度削除
$ docker rmi genzouw:5000/genzouw/hello:0.1

# Pullしてみる
$ docker pull genzouw:5000/genzouw/hello:0.1
0.1: Pulling from genzouw/hello
Digest: sha256:fa53d95510634d654c8e71dd94b69fb6285006201a6cd8753ba78527489b72d2
Status: Downloaded newer image for genzouw:5000/genzouw/hello:0.1
genzouw:5000/genzouw/hello:0.1

# イメージを実行
$ docker run genzouw:5000/genzouw/hello:0.1
Hello World

正しく動作しました。

ひとこと

改めてお話すると、Google Cloud Platform の GCR や AWS の ECR が缶コーヒー買うぐらいの費用で利用できるので、わざわざこの労力を使うメリットは有りません。
仕組みを理解する意味で触ってみるぐらいでしょう。