Google Container Registry (GCR) にDockerイメージをプッシュしようとしてエラーが出たときの対応

Docker,GCP

はじめに

独自にビルドした Docker イメージは、Docker Repository に配置して初めて自分以外も利用できるようになります。
パブリックな Docker Repository としては Docker Hub が有名です。
プライベートな Docker Repository として、僕は Google Container Registry (GCR) を利用することが多いです。

先日、他の方から 「Google Container Registry にビルドイメージをプッシュしようとしてエラーが出てハマっている」という相談がありましたので、こちらの解消方法について解説しようと思いました。

検証環境

[vagrant@localhost ~]$ cat /etc/os-release | head -n 7
NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="7"
PRETTY_NAME="CentOS Linux 7 (Core)"
ANSI_COLOR="0;31"

作業の流れ

  1. gcloud + docker コマンドのインストール
  2. Docker イメージを作成
  3. GCR に対する認証を設定
  4. Docker イメージをプッシュ
  5. Docker イメージをプル + 実行

1. gcloud + docker コマンドのインストール

gcloud は "Google Container Registry (GCR)" に対して「認証」を行うために利用します。

GCR に対する「認証」が完了すれば、そのホストから docker コマンドを使って Docker イメージをビルドし、プッシュできます。

それでは、 gcloud コマンドのインストールから行います。
( 本題ではないのでさらっと進みます。 )

$ sudo tee -a /etc/yum.repos.d/google-cloud-sdk.repo << EOM
[google-cloud-sdk]
name=Google Cloud SDK
baseurl=https://packages.cloud.google.com/yum/repos/cloud-sdk-el8-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=0
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg
       https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOM

$ sudo yum install -y google-cloud-sdk

次に、 docker コマンドのインストールを行います。
( 同様に本題ではないのでさらっと進みます。 )

# 一旦古いDockerパッケージを削除
$ sudo yum remove -y docker \
   docker-client \
   docker-client-latest \
   docker-common \
   docker-latest \
   docker-latest-logrotate \
   docker-logrotate \
   docker-engine

$ sudo yum install -y yum-utils

$ sudo yum-config-manager \
   --add-repo \
   https://download.docker.com/linux/centos/docker-ce.repo

$ sudo yum install -y docker-ce docker-ce-cli containerd.io

# Dockerデーモンの起動
$ sudo systemctl start docker
$ sudo systemctl enable docker

# おまじない(ログインユーザである vagrant が dockerグループに所属していないとDockerを操作できない)
$ sudo gpasswd -a vagrant docker
Adding user vagrant to group docker

# 一旦ログアウト
$ exit
logout
Connection to 127.0.0.1 closed.

改めて Vagrant 環境に ssh でログインし、 docker コマンドが利用できるようになっているか確認してみます。

公式の hello-world という Docker イメージを実行してみます。

$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
2db29710123e: Pull complete
Digest: sha256:507ecde44b8eb741278274653120c2bf793b174c06ff4eaa672b713b3263477b
Status: Downloaded newer image for hello-world:latest

Hello from Docker!

2. Docker イメージを作成

Docker イメージをビルドします。

適当な Dockerfile を作成します。

# 適当な Dockerfile を作成
$ cat <<'EOF' >Dockerfile
FROM alpine:3.11.3

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

CMD ["echo", "hello world! bye!"]
EOF

# できているか確認
$ cat Dockerfile
FROM alpine:3.11.3

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

CMD ["echo", "hello world! bye!"]

# ビルド
$ docker build -t gcr.io/genzouw-com/hello-gcr-iamge .
[+] Building 0.1s (5/5) FINISHED
 => [internal] load build definition from Dockerfile                                         0.0s
 => => transferring dockerfile: 139B                                                         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
 => CACHED [1/1] FROM docker.io/library/alpine:3.11.3                                        0.0s
 => exporting to image                                                                       0.0s
 => => exporting layers                                                                      0.0s
 => => writing image sha256:25d62b7f757b9bd3575b3b86ffa247315bb1c4a8a7b1e0fda9eadbae991eaaf0 0.0s
 => => naming to gcr.io/genzouw-com/hello-gcr-iamge                                          0.0s

GCR にイメージをプッシュする場合は、ビルド時に gcr.io/<GCPプロジェクト名>/<イメージ名>:<タグ> というイメージ名でビルドする必要があります。

今回は タグを省略しています が、この場合自動的に latest というタグが付与されます。

今回利用する僕の GCP プロジェクトは genzouw-com となりましたが、ここは各自お使いの GCP プロジェクト名を指定しましょう。

ビルドしたばかりのイメージを実行してみます。

$ docker run gcr.io/genzouw-com/hello-gcr-iamge
hello world! bye!

メッセージが表示され、 問題なく動作することがわかりました。

作成されたイメージをプッシュしてみます。

$ docker push gcr.io/genzouw-com/hello-gcr-iamge
Using default tag: latest
The push refers to repository [gcr.io/genzouw-com/hello-gcr-iamge]
5216338b40a7: Preparing
unauthorized: You don't have the needed permissions to perform this operation, and you may have invalid credentials. To authenticate your request, follow the steps in: https://cloud.google.com/container-registry/docs/advanced-authentication

当然ですが、GCR に対する認証エラーとなります。 (エラーにならなかったらだれでも GCR を更新できてしまうので問題です。)

3. GCR に対する認証設定

GCR にイメージをプッシュできるように認証を行います。

gcloud コマンドを使うための初期設定を行います。

# まずは `gcloud` コマンドを利用するための設定。
# 表示されたURLをブラウザで開き、表示されたコードを貼り付ける
$ gcloud auth login
Your browser has been opened to visit:

    https://accounts.google.com/o/oauth2/auth?response_type=code&**************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************

You are now logged in as [genzouw@gamil.com].
Your current project is [None].  You can change this setting by running:
  $ gcloud config set project PROJECT_ID

# 操作する GCP プロジェクト名を設定します。
$ gcloud config set project genzouw-com
Updated property [core/project].

GCR に対してビルドイメージをプッシュできるように認証を行います。

$ gcloud auth configure-docker
Adding credentials for all GCR repositories.
WARNING: A long list of credential helpers may cause delays running 'docker build'. We recommend passing the registry name to configure only the registry you are using.
After update, the following will be written to your Docker config file located at [/home/vagrant/.docker/config.json]:
 {
  "credHelpers": {
    "gcr.io": "gcloud",
    "us.gcr.io": "gcloud",
    "eu.gcr.io": "gcloud",
    "asia.gcr.io": "gcloud",
    "staging-k8s.gcr.io": "gcloud",
    "marketplace.gcr.io": "gcloud"
  }
}

Do you want to continue (Y/n)?  y

Docker configuration file updated.

完了しました。

認証設定は ~/.docker/config.json に保存されます。

4. Docker イメージをプッシュ

再度 GCR にイメージをプッシュします。

$ docker push gcr.io/genzouw-com/hello-gcr-iamge
Using default tag: latest
The push refers to repository [gcr.io/genzouw-com/hello-gcr-iamge]
5216338b40a7: Layer already exists
latest: digest: sha256:**************************************************************** size: 528

先程は失敗しましたが、今度はプッシュできました。

しかし、環境によっては このタイミングでまだエラーが発生する場合があります。

GCR の実態は Google Cloud Storage というストレージサービスですが、 Cloud Storage のバケット( 以下を参照 )に対する書き込み権限( roles/storage.legacyBucketWriter )がないと docker push 実行時にエラーが発生します。

  • gs://artifacts.<GCPプロジェクト名>.appspot.com

今回は gs://artifacts.genzouw-com.appspot.com というバケットに対して書き込みが行われることとなります。
幸いにも僕は上記のバケットに対して書き込み権限がありました。

権限が付与されていない場合には割当が必要です。
設定は Cloud Storage の管理ページから行ってください。 (ごめんなさい。大事なことなのですが今回は割愛します。エントリの作成作業に力尽きてきました。)

5. Docker イメージをプル + 実行

最後に動作確認のため、GCR からイメージをプルしてみましょう。

ローカルにイメージが残っているとそちらが利用され、GCR との通信が行われません。

動作確認を目的としているので、残っているイメージを事前に削除します。

$ docker rmi -f gcr.io/genzouw-com/hello-gcr-iamge
Untagged: gcr.io/genzouw-com/hello-gcr-iamge:latest
Untagged: gcr.io/genzouw-com/hello-gcr-iamge@sha256:d7f0a843a7e94693550d533dd8de79be406e23fd60e9f30fa7b73bb05c58ddaa
Deleted: sha256:25d62b7f757b9bd3575b3b86ffa247315bb1c4a8a7b1e0fda9eadbae991eaaf0

削除できたら、 docker run で実行します。

ローカルにイメージがないため、初回のみ GCR から自動的にプルします。

$ docker run gcr.io/genzouw-com/hello-gcr-iamge
Unable to find image 'gcr.io/genzouw-com/hello-gcr-iamge:latest' locally
latest: Pulling from genzouw-com/hello-gcr-iamge
c9b1b535fdd9: Already exists
Digest: sha256:d7f0a843a7e94693550d533dd8de79be406e23fd60e9f30fa7b73bb05c58ddaa
Status: Downloaded newer image for gcr.io/genzouw-com/hello-gcr-iamge:latest
hello world! bye!

正常に動作しました!

ひとこと

Cloud Storage への書き込み権限の設定が非常に大事なのにも関わらず、省略してしまいました。

ごめんなさい。エントリをまとめているうちに力尽きたというのもあります。

この点については「サービスアカウント」を作成してそちらを利用するのが良いのですがその方法についてはまた別途紹介します。

Docker,GCP