Ubuntu18.04でプライベートDockerレジストリを設定する方法
著者は、 Apache Software Foundation を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。
序章
Docker Registry は、Dockerコンテナイメージの保存と配信を管理するアプリケーションです。 レジストリはコンテナイメージを一元化し、開発者のビルド時間を短縮します。 Dockerイメージは、仮想化を通じて同じランタイム環境を保証しますが、イメージの構築には多大な時間の投資が必要になる場合があります。 たとえば、Dockerを使用するために依存関係とパッケージを別々にインストールするのではなく、開発者は必要なすべてのコンポーネントを含むレジストリから圧縮イメージをダウンロードできます。 さらに、開発者は、 TravisCI などの継続的インテグレーションツールを使用してイメージをレジストリにプッシュすることを自動化し、本番および開発中にイメージをシームレスに更新できます。
Dockerには、カスタムDockerイメージをホストできる無料のパブリックレジストリ Docker Hub もありますが、イメージを公開したくない場合があります。 通常、イメージにはアプリケーションの実行に必要なすべてのコードが含まれているため、プロプライエタリソフトウェアを使用する場合はプライベートレジストリを使用することをお勧めします。
このチュートリアルでは、独自のプライベートDockerレジストリを設定して保護します。 Docker Compose を使用して、Dockerアプリケーションを実行するための構成を定義し、Nginxを使用してサーバートラフィックをHTTPSから実行中のDockerコンテナーに転送します。 このチュートリアルを完了すると、カスタムDockerイメージをプライベートレジストリにプッシュし、リモートサーバーからイメージを安全にプルできるようになります。
前提条件
このガイドを開始する前に、次のものが必要です。
- Ubuntu18.04初期サーバーセットアップガイドに従ってセットアップされた2つのUbuntu18.04サーバー(sudo非rootユーザーとファイアウォールを含む)。 1つのサーバーはプライベートDockerレジストリをホストし、もう1つのサーバーはクライアントサーバーになります。
- DockerとDocker-Composeを両方のサーバーにインストールするには、 How to Install Docker-Compose on Ubuntu18.04チュートリアルに従ってください。 Docker Composeをインストールするには、このチュートリアルの最初のステップを完了するだけです。 このチュートリアルでは、前提条件の一部としてDockerをインストールする方法について説明します。
- Ubuntu 18.04にNginxをインストールする方法に従って、プライベートDockerレジストリサーバーにNginxをインストールします。
- Let’s EncryptでNginxを保護する方法に従って、プライベートDockerレジストリ用にサーバー上でLet’sEncryptで保護されたNginx。 手順4で、すべてのトラフィックをHTTPからHTTPSにリダイレクトしてください。
- プライベートDockerレジストリに使用しているサーバーに解決されるドメイン名。 これは、Let’sEncryptの前提条件の一部として設定します。
ステップ1—Dockerレジストリのインストールと構成
Dockerコマンドラインツールは、1つまたは2つのDockerコンテナーを起動および管理するのに役立ちますが、完全にデプロイするには、Dockerコンテナー内で実行されるほとんどのアプリケーションで、他のコンポーネントを並行して実行する必要があります。 たとえば、多くのWebアプリケーションは、アプリケーションのコードを提供するNginxなどのWebサーバー、PHPなどの解釈されたスクリプト言語、およびMySQLなどのデータベースサーバーで構成されています。
Docker Composeを使用すると、1つの.yml
ファイルを記述して、各コンテナーの構成と、コンテナーが相互に通信するために必要な情報をセットアップできます。 次に、docker-compose
コマンドラインツールを使用して、アプリケーションを構成するすべてのコンポーネントにコマンドを発行できます。
Dockerレジストリ自体は複数のコンポーネントを備えたアプリケーションであるため、DockerComposeを使用して構成を管理します。 レジストリのインスタンスを開始するには、docker-compose.yml
ファイルを設定して、レジストリがデータを保存する場所を定義します。
プライベートDockerレジストリをホストするために作成したサーバーで、docker-registry
ディレクトリを作成してそこに移動し、次のコマンドを使用してdata
サブフォルダーを作成できます。
- mkdir ~/docker-registry && cd $_
- mkdir data
テキストエディタを使用して、docker-compose.yml
構成ファイルを作成します。
- nano docker-compose.yml
Dockerレジストリの基本構成を説明する次のコンテンツをファイルに追加します。
version: '3'
services:
registry:
image: registry:2
ports:
- "5000:5000"
environment:
REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
volumes:
- ./data:/data
environment
セクションは、パス/data
を使用してDockerレジストリコンテナーに環境変数を設定します。 Docker Registryアプリケーションは、起動時にこの環境変数をチェックし、その結果、データを/data
フォルダーに保存し始めます。
ただし、volumes: - ./data:/data
行を含めると、Dockerはそのコンテナー内の/data
ディレクトリをレジストリサーバー上の/data
にマップし始めます。 最終的に、Dockerレジストリのデータはすべてレジストリサーバーの~/docker-registry/data
に保存されます。
ports
セクションは、構成5000:5000
で、サーバーのポート5000
を実行中のコンテナーのポート5000
にマップするようにDockerに指示します。 これにより、サーバーのポート5000
に要求を送信し、その要求をレジストリアプリケーションに転送することができます。
これで、DockerComposeを起動してセットアップを確認できます。
- docker-compose up
DockerがDocker自身のレジストリからDockerレジストリイメージをダウンロードしていることを示すダウンロードバーが出力に表示されます。 1〜2分以内に、次のような出力が表示されます(バージョンは異なる場合があります)。
Output of docker-compose upStarting docker-registry_registry_1 ... done
Attaching to docker-registry_registry_1
registry_1 | time="2018-11-06T18:43:09Z" 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.6 instance.id=c63483ee-7ad5-4205-9e28-3e809c843d42 version=v2.6.2
registry_1 | time="2018-11-06T18:43:09Z" level=info msg="redis not configured" go.version=go1.7.6 instance.id=c63483ee-7ad5-4205-9e28-3e809c843d42 version=v2.6.2
registry_1 | time="2018-11-06T18:43:09Z" level=info msg="Starting upload purge in 20m0s" go.version=go1.7.6 instance.id=c63483ee-7ad5-4205-9e28-3e809c843d42 version=v2.6.2
registry_1 | time="2018-11-06T18:43:09Z" level=info msg="using inmemory blob descriptor cache" go.version=go1.7.6 instance.id=c63483ee-7ad5-4205-9e28-3e809c843d42 version=v2.6.2
registry_1 | time="2018-11-06T18:43:09Z" level=info msg="listening on [::]:5000" go.version=go1.7.6 instance.id=c63483ee-7ad5-4205-9e28-3e809c843d42 version=v2.6.2
このチュートリアルの後半で、No HTTP secret provided
警告メッセージに対処します。 出力は、コンテナーが開始中であることを示しています。 出力の最後の行は、ポート5000
で正常にリッスンを開始したことを示しています。
デフォルトでは、Docker Composeは入力を待機し続けるため、CTRL+C
を押してDockerレジストリコンテナーをシャットダウンします。
ポート5000
でリッスンする完全なDockerレジストリを設定しました。 この時点で、手動で起動しない限り、レジストリは起動しません。 また、Docker Registryには認証メカニズムが組み込まれていないため、現在は安全ではなく、完全に公開されています。 次の手順では、これらのセキュリティ上の懸念に対処します。
ステップ2—Nginxポートフォワーディングの設定
Nginxを使用してDockerレジストリサーバーにHTTPSを設定しました。これは、Nginxからポート5000
へのポート転送を設定できることを意味します。 この手順を完了すると、example.comからレジストリに直接アクセスできます。
Let’s Encrypt の前提条件でNginxを保護する方法の一部として、サーバー構成を含む/etc/nginx/sites-available/example.com
ファイルを既に設定しています。
このファイルをテキストエディタで開きます。
- sudo nano /etc/nginx/sites-available/example.com
既存のlocation
行を見つけます。 次のようになります。
...
location / {
...
}
...
レジストリが実行されるポート5000
にトラフィックを転送する必要があります。 また、レジストリへの要求にヘッダーを追加する必要があります。これにより、各要求と応答でサーバーからの追加情報が提供されます。 location
セクションのコンテンツを削除し、そのセクションに次のコンテンツを追加します。
...
location / {
# Do not allow connections from docker 1.5 and earlier
# docker pre-1.6.0 did not properly set the user agent on ping, catch "Go *" user agents
if ($http_user_agent ~ "^(docker\/1\.(3|4|5(?!\.[0-9]-dev))|Go ).*$" ) {
return 404;
}
proxy_pass http://localhost:5000;
proxy_set_header Host $http_host; # required for docker client's sake
proxy_set_header X-Real-IP $remote_addr; # pass on real client's IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 900;
}
...
$http_user_agent
セクションは、クライアントのDockerバージョンが1.5
より上であることを確認し、UserAgent
がGo
アプリケーションではないことを確認します。 レジストリのバージョン2.0
を使用しているため、古いクライアントはサポートされていません。 詳細については、nginx
ヘッダー構成をDocker’sRegistryNginxガイドにあります。
ファイルを保存して終了します。 Nginxを再起動して変更を適用します。
- sudo service nginx restart
レジストリを実行することで、Nginxがトラフィックをポート5000
に転送していることを確認できます。
- cd ~/docker-registry
- docker-compose up
ブラウザウィンドウで、次のURLを開きます。
https://example.com/v2
空のJSONオブジェクトが表示されます。または:
{}
ターミナルでは、次のような出力が表示されます。
Output of docker-compose upregistry_1 | time="2018-11-07T17:57:42Z" level=info msg="response completed" go.version=go1.7.6 http.request.host=cornellappdev.com http.request.id=a8f5984e-15e3-4946-9c40-d71f8557652f http.request.method=GET http.request.remoteaddr=128.84.125.58 http.request.uri="/v2/" http.request.useragent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/604.4.7 (KHTML, like Gecko) Version/11.0.2 Safari/604.4.7" http.response.contenttype="application/json; charset=utf-8" http.response.duration=2.125995ms http.response.status=200 http.response.written=2 instance.id=3093e5ab-5715-42bc-808e-73f310848860 version=v2.6.2
registry_1 | 172.18.0.1 - - [07/Nov/2018:17:57:42 +0000] "GET /v2/ HTTP/1.0" 200 2 "" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/604.4.7 (KHTML, like Gecko) Version/11.0.2 Safari/604.4.7"
最後の行から、ブラウザからリクエストを送信したエンドポイントである/v2/
に対してGET
リクエストが行われたことがわかります。 コンテナは、ポートフォワーディングからの要求を受信し、{}
の応答を返しました。 出力の最後の行にあるコード200
は、コンテナーが要求を正常に処理したことを意味します。
ポートフォワーディングを設定したので、レジストリのセキュリティの向上に進むことができます。
ステップ3—認証の設定
Nginxプロキシリクエストを適切に使用すると、HTTP認証を使用してレジストリを保護し、Dockerレジストリにアクセスできるユーザーを管理できるようになります。 これを実現するには、htpasswd
を使用して認証ファイルを作成し、それにユーザーを追加します。 HTTP認証は、レジストリが使用するHTTPS接続を介してすばやくセットアップして保護します。
次のコマンドを実行して、htpasswd
パッケージをインストールできます。
- sudo apt install apache2-utils
次に、認証資格情報を保存するディレクトリを作成し、そのディレクトリに移動します。 $_
は、前のコマンドの最後の引数(この場合は~/docker-registry/auth
)に展開されます。
- mkdir ~/docker-registry/auth && cd $_
次に、次のように最初のユーザーを作成し、username
を使用するユーザー名に置き換えます。 -B
フラグは、bcrypt
暗号化を指定します。これは、デフォルトの暗号化よりも安全です。 プロンプトが表示されたら、パスワードを入力します。
- htpasswd -Bc registry.password username
注:ユーザーを追加するには、-cオプションを指定せずに前のコマンドを再実行します(c
は作成用です)。
- htpasswd registry.password username
次に、docker-compose.yml
ファイルを編集して、作成したファイルを使用してユーザーを認証するようにDockerに指示します。
- cd ~/docker-registry
- nano docker-compose.yml
docker-compose.yml
ファイルを編集してDockerにユーザーの認証方法を指示することにより、作成したauth/
ディレクトリの環境変数とボリュームを追加できます。 次の強調表示されたコンテンツをファイルに追加します。
version: '3'
services:
registry:
image: registry:2
ports:
- "5000:5000"
environment:
REGISTRY_AUTH: htpasswd
REGISTRY_AUTH_HTPASSWD_REALM: Registry
REGISTRY_AUTH_HTPASSWD_PATH: /auth/registry.password
REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
volumes:
- ./auth:/auth
- ./data:/data
REGISTRY_AUTH
には、使用している認証方式であるhtpasswd
を指定し、認証ファイルのパスにREGISTRY_AUTH_HTPASSWD_PATH
を設定しています。 最後に、REGISTRY_AUTH_HTPASSWD_REALM
はhtpasswd
レルムの名前を示します。
これで、レジストリを実行し、ユーザーにユーザー名とパスワードの入力を求めるプロンプトが表示されることを確認することで、認証が正しく機能することを確認できます。
- docker-compose up
ブラウザウィンドウで、https://example.com/v2
を開きます。
username
と対応するパスワードを入力すると、{}
がもう一度表示されます。 基本認証の設定を確認しました。レジストリは、正しいユーザー名とパスワードを入力した後にのみ結果を返しました。 これでレジストリが保護され、引き続きレジストリを使用できます。
ステップ4—サービスとしてのDockerレジストリの開始
システムが起動するたびにレジストリが確実に起動するようにする必要があります。 予期しないシステムクラッシュが発生した場合は、サーバーの再起動時にレジストリが再起動することを確認する必要があります。 docker-compose.yml
を開きます:
- nano docker-compose.yml
registry:
の下に次のコンテンツ行を追加します。
...
registry:
restart: always
...
レジストリをバックグラウンドプロセスとして開始できます。これにより、ssh
セッションを終了し、プロセスを永続化できます。
- docker-compose up -d
レジストリをバックグラウンドで実行すると、ファイルアップロード用にNginxを準備できるようになります。
ステップ5—Nginxのファイルアップロードサイズを増やす
イメージをレジストリにプッシュする前に、レジストリが大きなファイルのアップロードを処理できることを確認する必要があります。 Dockerは、大きな画像のアップロードを別々のレイヤーに分割しますが、1GB
を超える場合があります。 デフォルトでは、Nginxのファイルアップロードには1MB
の制限があるため、nginx
の構成ファイルを編集し、ファイルの最大アップロードサイズを2GB
に設定する必要があります。
- sudo nano /etc/nginx/nginx.conf
http
セクションを見つけて、次の行を追加します。
...
http {
client_max_body_size 2000M;
...
}
...
最後に、Nginxを再起動して、構成の変更を適用します。
- sudo service nginx restart
Nginxエラーなしで大きな画像をDockerレジストリにアップロードできるようになりました。
ステップ6—プライベートDockerレジストリへの公開
これで、プライベートDockerレジストリにイメージを公開する準備が整いましたが、最初にイメージを作成する必要があります。 このチュートリアルでは、DockerHubのubuntu
イメージに基づいて単純なイメージを作成します。 Docker Hubは、パブリックにホストされているレジストリであり、アプリケーションをすばやくDocker化するために利用できる多くの事前構成されたイメージがあります。 ubuntu
イメージを使用して、レジストリへのプッシュとプルをテストします。
クライアントサーバーから、新しいレジストリにプッシュする小さな空のイメージを作成します。-i
および-t
フラグを使用すると、コンテナへのインタラクティブなシェルアクセスが可能になります。
- docker run -t -i ubuntu /bin/bash
ダウンロードが完了すると、Dockerプロンプトが表示されます。root@
に続くコンテナIDは異なることに注意してください。 SUCCESS
というファイルを作成して、ファイルシステムをすばやく変更します。 次のステップでは、このファイルを使用して、公開プロセスが成功したかどうかを判断できます。
- touch /SUCCESS
Dockerコンテナを終了します。
- exit
次のコマンドは、すでに実行されているイメージと行った変更に基づいて、test-image
という新しいイメージを作成します。 この場合、/SUCCESS
ファイルの追加が新しいイメージに含まれています。
変更をコミットします。
- docker commit $(docker ps -lq) test-image
この時点では、イメージはローカルにのみ存在します。 これで、作成した新しいレジストリにプッシュできます。 Dockerレジストリにログインします。
- docker login https://example.com
以前のusername
と対応するパスワードを入力します。 次に、イメージにプッシュするために、プライベートレジストリの場所でイメージにタグを付けます。
- docker tag test-image example.com/test-image
新しくタグ付けされた画像をレジストリにプッシュします。
- docker push example.com/test-image
出力は次のようになります。
OutputThe push refers to a repository [example.com/test-image]
e3fbbfb44187: Pushed
5f70bf18a086: Pushed
a3b5c80a4eba: Pushed
7f18b442972b: Pushed
3ce512daaf78: Pushed
7aae4540b42d: Pushed
...
レジストリがユーザー認証を処理し、認証されたユーザーがイメージをレジストリにプッシュできることを確認しました。 次に、レジストリからもイメージをプルできることを確認します。
ステップ7—プライベートDockerレジストリからプルする
レジストリサーバーに戻り、クライアントサーバーからのイメージのプルをテストできるようにします。 3番目のサーバーからこれをテストすることも可能です。
以前に設定したユーザー名とパスワードでログインします。
- docker login https://example.com
これで、画像をプルする準備が整いました。 前の手順でタグ付けしたドメイン名と画像名を使用します。
- docker pull example.com/test-image
Dockerはイメージをダウンロードし、プロンプトに戻ります。 レジストリサーバーでイメージを実行すると、前に作成したSUCCESS
ファイルがそこにあることがわかります。
- docker run -it example.com/test-image /bin/bash
bashシェル内にファイルをリストします。
- ls
このイメージ用に作成したSUCCESS
ファイルが表示されます。
SUCCESS bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
これで、ユーザーがカスタムイメージをプッシュおよびプルできる安全なレジストリの設定が完了しました。
結論
このチュートリアルでは、独自のプライベートDockerレジストリを設定し、Dockerイメージを公開しました。 はじめに述べたように、 TravisCI または同様のCIツールを使用して、プライベートレジストリへの直接プッシュを自動化することもできます。 Dockerとレジストリをワークフローに活用することで、コードを含むイメージが、本番環境でも開発環境でも、どのマシンでも同じ動作をするようにすることができます。 Dockerファイルの作成の詳細については、このDockerチュートリアルでプロセスを説明しています。