著者は、 Write for DOnations プログラムの一環として、 Free and Open SourceFundを選択して寄付を受け取りました。

序章

Docker Registry は、Dockerコンテナイメージの保存と配信を管理するアプリケーションです。 レジストリはコンテナイメージを一元化し、開発者のビルド時間を短縮します。 Dockerイメージは、仮想化を通じて同じランタイム環境を保証しますが、イメージの構築には多大な時間の投資が必要になる場合があります。 たとえば、Dockerを使用するために依存関係とパッケージを別々にインストールするのではなく、開発者は必要なすべてのコンポーネントを含むレジストリから圧縮イメージをダウンロードできます。 さらに、開発者は、 TravisCI などの継続的インテグレーションツールを使用してイメージをレジストリにプッシュすることを自動化し、本番および開発中にイメージをシームレスに更新できます。

Dockerには、カスタムDockerイメージをホストできる無料のパブリックレジストリ Docker Hub もありますが、イメージを公開したくない場合があります。 通常、イメージにはアプリケーションの実行に必要なすべてのコードが含まれているため、プロプライエタリソフトウェアを使用する場合はプライベートレジストリを使用することをお勧めします。

このチュートリアルでは、独自のプライベートDockerレジストリを設定して保護します。 Docker Compose を使用してDockerコンテナーを実行する構成を定義し、Nginxを使用してサーバートラフィックをインターネットから実行中のDockerコンテナーに転送します。 このチュートリアルを完了すると、カスタムDockerイメージをプライベートレジストリにプッシュし、リモートサーバーからイメージを安全にプルできるようになります。

前提条件

  • Ubuntu 20.04初期サーバーセットアップガイドに従ってセットアップされた2つのUbuntu20.04サーバー(sudo非rootユーザーとファイアウォールを含む)。 1台のサーバーはプライベートDockerレジストリをホストし、もう1台はクライアントサーバーになります。
  • Ubuntu 20.04にDockerをインストールして使用する方法のステップ1と2に従って、両方のサーバーにDockerをインストールします。
  • Ubuntu 20.04にDockerComposeをインストールして使用する方法のステップ1に従って、hostサーバーにDockerComposeをインストールします。
  • Ubuntu 20.04 にNginxをインストールする方法の手順に従って、hostサーバーにNginxをインストールします。
  • Ubuntu 20.04チュートリアルでLet’sEncryptを使用してNginxを保護する方法に従って、プライベートDockerレジストリのサーバーでLet’sEncryptを使用してNginxを保護します。 ステップ4で、すべてのトラフィックをHTTPからHTTPSにリダイレクトしてください。
  • プライベートDockerレジストリに使用しているサーバーに解決されるドメイン名。 これは、Let’sEncryptの前提条件の一部として設定します。 このチュートリアルでは、これをyour_domainと呼びます。

ステップ1—Dockerレジストリのインストールと構成

コマンドラインのDockerは、コンテナーを開始してテストするときに役立ちますが、複数のコンテナーが並行して実行される大規模なデプロイメントでは扱いにくいことがわかります。

Docker Composeを使用すると、1つの.ymlファイルを記述して、各コンテナーの構成と、コンテナーが相互に通信するために必要な情報をセットアップできます。 docker-composeコマンドラインツールを使用して、アプリケーションを構成するすべてのコンポーネントにコマンドを発行し、それらをグループとして制御できます。

Docker Registryはそれ自体が複数のコンポーネントを備えたアプリケーションであるため、DockerComposeを使用して管理します。 レジストリのインスタンスを開始するには、docker-compose.ymlファイルを設定して、レジストリと、レジストリがデータを保存するディスク上の場所を定義します。

メインサーバーのdocker-registryというディレクトリに構成を保存します。 次のコマンドを実行して作成します。

  1. mkdir ~/docker-registry

そこに移動します:

  1. cd ~/docker-registry

次に、dataというサブディレクトリを作成します。このサブディレクトリに、レジストリにイメージが保存されます。

  1. mkdir data

次のコマンドを実行して、docker-compose.ymlというファイルを作成して開きます。

  1. nano docker-compose.yml

Dockerレジストリの基本インスタンスを定義する次の行を追加します。

〜/ docker-registry / docker-compose.yml
version: '3'

services:
  registry:
    image: registry:2
    ports:
    - "5000:5000"
    environment:
      REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
    volumes:
      - ./data:/data

まず、最初のサービスにregistryという名前を付け、そのイメージをregistryバージョン2に設定します。 次に、portsで、ホストのポート5000をコンテナーのポート5000にマップします。 これにより、サーバーのポート5000にリクエストを送信し、リクエストをレジストリに転送することができます。

environmentセクションで、REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY変数を/dataに設定し、データを保存するボリュームを指定します。 次に、volumesセクションで、ホストファイルシステムの/dataディレクトリを、パススルーとして機能するコンテナ内の/dataにマップします。 データは実際にはホストのファイルシステムに保存されます。

ファイルを保存して閉じます。

これで、以下を実行して構成を開始できます。

  1. docker-compose up

レジストリコンテナとその依存関係がダウンロードされ、開始されます。

Output
Creating network "docker-registry_default" with the default driver Pulling registry (registry:2)... 2: Pulling from library/registry e95f33c60a64: Pull complete 4d7f2300f040: Pull complete 35a7b7da3905: Pull complete d656466e1fe8: Pull complete b6cb731e4f93: Pull complete Digest: sha256:da946ca03fca0aade04a73aa94b54ff0dc614216bdd1d47585f97b4c1bdaa0e2 Status: Downloaded newer image for registry:2 Creating docker-registry_registry_1 ... done Attaching to docker-registry_registry_1 registry_1 | time="2021-03-18T12:32:59.587157744Z" 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=119fe50b-2bb6-4a8d-902d-dfa2db63fc2f service=registry version=v2.7.1 registry_1 | time="2021-03-18T12:32:59.587912733Z" level=info msg="redis not configured" go.version=go1.11.2 instance.id=119fe50b-2bb6-4a8d-902d-dfa2db63fc2f service=registry version=v2.7.1 registry_1 | time="2021-03-18T12:32:59.598496488Z" level=info msg="using inmemory blob descriptor cache" go.version=go1.11.2 instance.id=119fe50b-2bb6-4a8d-902d-dfa2db63fc2f service=registry version=v2.7.1 registry_1 | time="2021-03-18T12:32:59.601503005Z" level=info msg="listening on [::]:5000" go.version=go1.11.2 instance.id=119fe50b-2bb6-4a8d-902d-dfa2db63fc2f service=registry version=v2.7.1 ...

このチュートリアルの後半で、No HTTP secret provided警告メッセージに対処します。 出力の最後の行は、ポート5000で正常にリッスンを開始したことを示していることに注意してください。

CTRL+Cを押すと、実行を停止できます。

このステップでは、ポート5000でリッスンするDockerレジストリを開始するDockerCompose構成を作成しました。 次の手順では、ドメインで公開し、認証を設定します。

ステップ2—Nginxポートフォワーディングの設定

前提条件の一部として、ドメインでHTTPSを有効にしました。 保護されたDockerレジストリをそこで公開するには、ドメインからレジストリコンテナにトラフィックを転送するようにNginxを構成するだけで済みます。

サーバー構成を含む/etc/nginx/sites-available/your_domainファイルは既にセットアップされています。 次のコマンドを実行して編集用に開きます。

  1. sudo nano /etc/nginx/sites-available/your_domain

既存のlocationブロックを見つけます。

/ etc / nginx / sites-available / your_domain
...
location / {
  ...
}
...

レジストリがトラフィックをリッスンするポート5000にトラフィックを転送する必要があります。 また、レジストリに転送されるリクエストにヘッダーを追加します。これにより、リクエスト自体に関するサーバーからの追加情報が提供されます。 locationブロックの既存の内容を次の行に置き換えます。

/ etc / nginx / sites-available / your_domain
...
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;
}
...

ifブロックは、リクエストのユーザーエージェントをチェックし、Dockerクライアントのバージョンが1.5を超えていること、およびアクセスしようとしているのがGoアプリケーションではないことを確認します。 これに関する詳細については、nginxヘッダー構成をDockerのレジストリNginxガイドにあります。

完了したら、ファイルを保存して閉じます。 Nginxを再起動して変更を適用します。

  1. sudo systemctl restart nginx

エラーが発生した場合は、追加した構成を再確認してください。

Nginxがポート5000のレジストリコンテナにトラフィックを適切に転送していることを確認するには、次のコマンドを実行します。

  1. docker-compose up

次に、ブラウザウィンドウで、ドメインに移動し、次のようにv2エンドポイントにアクセスします。

https://your_domain/v2

空のJSONオブジェクトが表示されます。

{}

端末には、次のような出力が表示されます。

Output
registry_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は、コンテナーが要求を正常に処理したことを意味します。

CTRL+Cを押して実行を停止します。

ポートフォワーディングを設定したので、次にレジストリのセキュリティを向上させます。

ステップ3—認証の設定

Nginxを使用すると、管理するサイトにHTTP認証を設定できます。これを使用して、Dockerレジストリへのアクセスを制限できます。 これを実現するには、htpasswdを使用して認証ファイルを作成し、それに受け入れられるユーザー名とパスワードの組み合わせを追加します。

apache2-utilsパッケージをインストールすると、htpasswdユーティリティを入手できます。 実行してこれを行います:

  1. sudo apt install apache2-utils -y

~/docker-registry/authの下に資格情報を含む認証ファイルを保存します。 次のコマンドを実行して作成します。

  1. mkdir ~/docker-registry/auth

そこに移動します:

  1. cd ~/docker-registry/auth

最初のユーザーを作成し、usernameを使用するユーザー名に置き換えます。 -Bフラグは、Dockerが必要とするbcryptアルゴリズムの使用を指示します。

  1. htpasswd -Bc registry.password username

プロンプトが表示されたらパスワードを入力すると、資格情報の組み合わせがregistry.passwordに追加されます。

注:ユーザーを追加するには、-cを指定せずに前のコマンドを再実行すると、新しいファイルが作成されます。

  1. htpasswd -B registry.password username

資格情報のリストが作成されたので、docker-compose.ymlを編集して、作成したファイルを使用してユーザーを認証するようにDockerに指示します。 次のコマンドを実行して編集用に開きます。

  1. nano ~/docker-registry/docker-compose.yml

強調表示された行を追加します。

〜/ docker-registry / docker-compose.yml
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

HTTP認証の使用を指定する環境変数を追加し、作成されたファイルhtpasswdへのパスを指定しました。 REGISTRY_AUTHには、使用している認証方式である値としてhtpasswdを指定し、認証ファイルのパスにREGISTRY_AUTH_HTPASSWD_PATHを設定しています。 REGISTRY_AUTH_HTPASSWD_REALMは、htpasswdレルムの名前を示します。

また、./authディレクトリをマウントして、ファイルをレジストリコンテナ内で使用できるようにしました。 ファイルを保存して閉じます。

これで、認証が正しく機能することを確認できます。 まず、メインディレクトリに移動します。

  1. cd ~/docker-registry

次に、以下を実行してレジストリを実行します。

  1. docker-compose up

ブラウザで、ドメインのページを更新します。 ユーザー名とパスワードの入力を求められます。

クレデンシャルの有効な組み合わせを提供すると、空のJSONオブジェクトが表示されます。

{}

これは、認証が正常に行われ、レジストリにアクセスできることを意味します。 CTRL+Cを押して終了します。

これでレジストリが保護され、認証後にのみアクセスできるようになりました。 次に、自動的に起動することで再起動に耐えながら、バックグラウンドプロセスとして実行するように構成します。

ステップ4—サービスとしてのDockerレジストリの開始

Docker Composeに常に実行を継続するように指示することで、システムが起動するたびに、またはシステムがクラッシュした後に、レジストリコンテナーが確実に起動するようにすることができます。 docker-compose.ymlを開いて編集します。

  1. nano docker-compose.yml

registryブロックの下に次の行を追加します。

docker-compose.yml
...
  registry:
    restart: always
...

restartを設定して、コンテナーが再起動後も存続することを常に保証します。 完了したら、ファイルを保存して閉じます。

これで、-dを渡すことにより、バックグラウンドプロセスとしてレジストリを開始できます。

  1. docker-compose up -d

レジストリをバックグラウンドで実行すると、SSHセッションを自由に閉じることができ、レジストリは影響を受けません。

Dockerイメージのサイズが非常に大きい可能性があるため、Nginxがアップロードに受け入れる最大ファイルサイズを増やします。

ステップ5—Nginxのファイルアップロードサイズを増やす

イメージをレジストリにプッシュする前に、レジストリが大きなファイルのアップロードを処理できることを確認する必要があります。

Nginxでのファイルアップロードのデフォルトのサイズ制限は1mであり、Dockerイメージには十分ではありません。 これを上げるには、/etc/nginx/nginx.confにあるメインのNginx構成ファイルを変更します。 次のコマンドを実行して編集用に開きます。

  1. sudo nano /etc/nginx/nginx.conf

httpセクションを見つけて、次の行を追加します。

/etc/nginx/nginx.conf
...
http {
        client_max_body_size 16384m;
        ...
}
...

client_max_body_sizeパラメーターが16384mに設定され、最大アップロードサイズが16GBになりました。

完了したら、ファイルを保存して閉じます。

Nginxを再起動して、構成の変更を適用します。

  1. sudo systemctl restart nginx

Nginxが転送をブロックしたりエラーを発生させたりすることなく、大きな画像をDockerレジストリにアップロードできるようになりました。

ステップ6—プライベートDockerレジストリへの公開

Docker Registryサーバーが稼働し、大きなファイルサイズを受け入れるようになったので、イメージをサーバーにプッシュしてみてください。 すぐに利用できるイメージがないため、パブリックDockerレジストリであるDockerHubのubuntuイメージを使用してテストします。

2番目のクライアントサーバーから、次のコマンドを実行してubuntuイメージをダウンロードして実行し、そのシェルにアクセスします。

  1. docker run -t -i ubuntu /bin/bash

-iおよび-tフラグを使用すると、コンテナーへのインタラクティブなシェルアクセスが可能になります。

入ったら、次のコマンドを実行してSUCCESSというファイルを作成します。

  1. touch /SUCCESS

このファイルを作成することにより、コンテナをカスタマイズしました。 後でそれを使用して、まったく同じコンテナを使用していることを確認します。

次のコマンドを実行して、コンテナシェルを終了します。

  1. exit

次に、カスタマイズしたコンテナから新しいイメージを作成します。

  1. docker commit $(docker ps -lq) test-image

これで新しいイメージがローカルで利用可能になり、新しいコンテナレジストリにプッシュします。 まず、ログインする必要があります:

  1. docker login https://your_domain

プロンプトが表示されたら、このチュートリアルのステップ3で定義したユーザー名とパスワードの組み合わせを入力します。

出力は次のようになります。

Output
... Login Succeeded

ログインしたら、作成したイメージの名前を変更します。

  1. docker tag test-image your_domain/test-image

最後に、新しくタグ付けされた画像をレジストリにプッシュします。

  1. docker push your_domain/test-image

次のような出力が表示されます。

Output
The push refers to a repository [your_domain/test-image] 420fa2a9b12e: Pushed c20d459170d8: Pushed db978cae6a05: Pushed aeb3f02e9374: Pushed latest: digest: sha256:88e782b3a2844a8d9f0819dc33f825dde45846b1c5f9eb4870016f2944fe6717 size: 1150

レジストリがログインしてユーザー認証を処理し、認証されたユーザーが画像をレジストリにプッシュできることを確認しました。 次に、レジストリからイメージをプルしてみます。

ステップ7—プライベートDockerレジストリからプルする

イメージをプライベートレジストリにプッシュしたので、そこからプルしてみます。

メインサーバーで、前に設定したユーザー名とパスワードでログインします。

  1. docker login https://your_domain

次のコマンドを実行して、test-imageを引いてみてください。

  1. docker pull your_domain/test-image

Dockerはイメージをダウンロードする必要があります。 次のコマンドでコンテナを実行します。

  1. docker run -it your_domain/test-image /bin/bash

次のコマンドを実行して、存在するファイルを一覧表示します。

  1. ls

以前に作成したSUCCESSファイルが表示され、作成したものと同じイメージであることを確認できます。

SUCCESS  bin  boot  dev  etc  home  lib  lib64  media   mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

次のコマンドを実行して、コンテナシェルを終了します。

  1. exit

画像のプッシュとプルをテストしたので、カスタム画像の保存に使用できる安全なレジストリの設定が完了しました。

結論

このチュートリアルでは、独自のプライベートDockerレジストリを設定し、Dockerイメージを公開しました。 はじめに述べたように、 TravisCI または同様のCIツールを使用して、プライベートレジストリへの直接プッシュを自動化することもできます。 ワークフローでDockerコンテナーを活用することで、コードを含むイメージが、本番環境でも開発環境でも、どのマシンでも同じ動作をするようにすることができます。 Dockerファイルの作成の詳細については、ベストプラクティスに関する公式ドキュメントにアクセスしてください。