gcloud docker は Docker 18.03 以降をサポートしないらしい。

概要

  • gcloud auth configure-docker してやると、今まで gcloud docker -- image pull してたところを docker image pull でよくなる。

  • CoreOS で Google Cloud SDK を Docker から利用しているとややこしい。

  • gcloud auth print-access-token でトークンを取って docker login するしかなさそう。

  • (追記 : 2018/5/28) docker-credential-gcloud を自前で書いて PATH の通った /opt/bin に用意すると全てが綺麗に解決する。

詳細

4月くらいには気付いていたんだけど時間がなくて直せてなかったものを直した。

いつものように

gcloud docker -- image pull gcr.io/my-project/my_image:latest

などとしていたところ

WARNING: `gcloud docker` will not be supported for Docker client versions above 18.03.
Please use `gcloud auth configure-docker` to configure `docker` to use `gcloud` as a credential helper, then use `docker` as you would for non-GCR registries, e.g. `docker pull gcr.io/project-id/my-image`.
Add `--verbosity=error` to silence this warning, e.g. `gcloud docker --verbosity=error -- pull gcr.io/project-id/my-image`.
See: https://cloud.google.com/container-registry/docs/support/deprecation-notices#gcloud-docker

というエラーメッセージが出た。エラーメッセージが出るだけでまだ普通に動きはする。

察するに gcloud docker は deprecated となるのだろう。

gcloud auth configure-docker しろと書いてあるので素直にしてやると、

The following settings will be added to your Docker config file
located at [/Users/dnpp/.docker/config.json]:
 {
  "credHelpers": {
    "gcr.io": "gcloud",
    "us.gcr.io": "gcloud",
    "eu.gcr.io": "gcloud",
    "asia.gcr.io": "gcloud",
    "staging-k8s.gcr.io": "gcloud"
  }
}

Do you want to continue (Y/n)?

と出て、 Y してやれば ~/.docker/config.json に上記が追記され、素直に docker コマンドを使えるようになる。

これにて macOS 環境で gcloud docker を使うために無効にせざるを得なかった Docker for Mac の Securely store Docker logins in macOS keychain オプションを有効に出来るようになった。

めでたしめでたし。

しかし話はそんなに簡単ではない。

上記の話は、

  • macOS High Sierra 10.13.4
  • Docker for Mac
  • Google Cloud SDK を curl https://sdk.cloud.google.com | bash で直接インストール

の場合の話である。もうちょい詳細な環境は、

$ docker --version
Docker version 18.05.0-ce-rc1, build 33f00ce

$ which gcloud
/Users/dnpp/google-cloud-sdk/bin/gcloud

$ gcloud --version
Google Cloud SDK 200.0.0
beta 2017.09.15
bq 2.0.33
core 2018.04.30
gcloud
gsutil 4.31

こんな感じ。

手元にある MacBook Pro Retina 13-inch Late 2013 と Mac mini Late 2012 で 2 台とも同じ環境だったが、これは言われた通りにやれば上手く動く。大きな問題はない。

試してはいないが、恐らく gcloud が実体として PATH の通った場所に実行できる形で存在すれば他の環境でも問題はないものと思われる。

本題はここからである。

CoreOS で Google Cloud SDK を Docker から利用しているとややこしい。

Google Cloud SDK は Docker image でも配布されている。

docker image pull google/cloud-sdk:latest

そして CoreOS ではデフォルトでこちらを使うようになっている。

GCE で CoreOS のインスタンスを立ち上げると初期状態で gcloud コマンドが使えて結構驚くのだが、実体は alias のようだ。

alias gcloud="(docker images google/cloud-sdk || docker pull google/cloud-sdk) > /dev/null;docker run -ti --rm --net=host -v $HOME/.config:/root/.config -v /var/run/docker.sock:/var/run/docker.sock google/cloud-sdk gcloud"

実装自体は、昔調べた 2017/9 頃の話だと coreos/ignitioninternal/oem/oem.go にされていた。

2018/5 にこの記事を書くにあたって調べ直したところ、 coreos/coreos-overlay
coreos-base/oem-gce/files/files/google-cloud-sdk.sh の方に移動した様だ。

この間に、

  • -v $HOME/.config:/.config から /root が抜けていたバグ。
  • -v /var/run/docker.sock:/var/run/doker.sock にあった doker という typo
  • run--rm 指定がないので docker ps で見たときにゴミだらけになってた問題。

などの問題が解決されており、着実に進歩していた模様。

2017/9 頃の話なのだが、 gcloud docker -- image pulldoker という typo を直して /var/run/docker.sock をマウントするだけでは動かず、

  • /usr/bin/docker
  • /run/metadata
  • /run/torcx

をマウントするように追記する必要があった。

詳細は Ignition's "gcloud" alias doesn't work with torcx-ified docker #2112 にある。

しかし 2018/5 にこの記事を書くにあたって再び検証してみたところ /var/run/docker.sock のみのマウントで gcloud docker -- image pull が出来るようになっていたので、細かいところで色々な改修が行われているのだなぁという気持ちになった。

どうして動くようになったのか、余裕があれば後で追っておきたい。

本題

  • CoreOS から gcloud を何も考えずに使えるのは alias になっているからであり、コマンドではない。
    • Docker の credential helper として "gcloud" と設定したところで、シェルの alias なので永遠に見付けることができない。
  • alias で設定されている gcloud には -v $HOME/.docker:/root/.docker の指定がない。
    • つまり gcloud auth configure-docker の結果書き出される ~/.docker/config.json がホスト側に永続化されない。

といった問題があり、このままでは全く使えない。

失敗例

結論から言うとこれではダメだったが、とりあえず PATH の通ってる /opt/bin にラッパーシェルスクリプトを置けば良いのではと思ってやってみた。

CoreOS でそのような用途で使っても良いとされているのが /opt 以下で、 /opt/bin にはデフォルトで PATH が通っている。

/opt/bin/gcloud

#!/usr/bin/env bash

docker container run -it --rm --net=host \
-v $HOME/.config:/root/.config \
-v $HOME/.docker:/root/.docker \
-v /var/run/docker.sock:/var/run/docker.sock \
google/cloud-sdk gcloud "$@"

シェルスクリプトを書くときに $@ とか $* とかそれらを "" で囲ったときの違いが怖くて毎回ググってしまうが、ラッパーとして使うには多分これで良いはず。

この状態で gcloud auth configure-docker してやると ~/.docker/config.jsoncredHelpers が追記される。

もっとも gcloud auth configure-docker するだけなら ~/.docker/config.json に書き込み権限があるだけで良いので、

  • -v $HOME/.config:/root/.config
  • -v $HOME/.docker:/root/.docker

とマウントして、

docker container run -it --rm \
-v $HOME/.config:/root/.config \
-v $HOME/.docker:/root/.docker \
google/cloud-sdk gcloud auth configure-docker

これでも良さそうではあった。

とりあえず

  • gcloud ラッパースクリプトが存在している
  • それを "credHelpers" に指定している

という状況で docker image pull をしてみたが、これが失敗する。

$ docker image pull gcr.io/my-project/my_image:latest
Error response from daemon: 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

途中試行錯誤しつつ、

  • gcloud docker と打つたびに変更される ~/.docker/config.json のアクセス権が正しいか確認したり、
  • ラッパースクリプトから環境変数 $HOME が読めるか確認したり、
  • "credHelpers" に直接 sudo docker container run ... などと書いてみたり、
  • そもそも "credHelpers" に指定した gcloud が呼ばれているのか確認したのだが、

そもそも呼ばれてないことが分かった。

マジかよ。僕が勝手に思ってた "credHelpers" の挙動と違うじゃん。

試行錯誤してる中で見付けたのだが、 ~/.docker/config.json への書き込み権限がある状態で gcloud docker と打つと、サブコマンドなど一切打ってなくても ~/.docker/config.json"auths" が勝手に追記され、ついでにアクセス権も root:root600 になり、アクセス権を正しくしてやると、それ以降は docker image pull に成功するということも分かり、とにかく完全につらい気持ちになった。

成功例 その1 (諦めて docker login する)

エラーメッセージにも書いてあるので、素直に 公式ドキュメント を読みにいくと docker login する方法が書いてあった。

gcloud auth application-default print-access-token

これでトークンが取れると書いてあるが以下のエラーが出る。

ERROR: (gcloud.auth.application-default.print-access-token)
The Application Default Credentials are not available. They are available if running in Google Compute Engine.
Otherwise, the environment variable GOOGLE_APPLICATION_CREDENTIALS must be defined pointing to a file defining the credentials.
See https://developers.google.com/accounts/docs/application-default-credentials for more information.

色々見ていると application-default は別に無くても良さそうなので

gcloud auth print-access-token

とすると取れた。

実際には docker container run --rm -v $HOME/.config:/root/.config google/cloud-sdk という prefix が付いている。

GCLOUD_ACCESS_TOKEN=$(docker container run --rm \
-v $HOME/.config:/root/.config \
google/cloud-sdk \
gcloud auth print-access-token)
$ docker login -u oauth2accesstoken -p "$GCLOUD_ACCESS_TOKEN" 'https://gcr.io'

WARNING! Using --password via the CLI is insecure. Use --password-stdin.
WARNING! Your password will be stored unencrypted in /home/core/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Are you sure you want to proceed? [y/N]

-p でパスワード指定をすると「やめろ!」って言われつつ [y/N] のプロンプトが出てしまう。

言われた通りに --password-stdin してやると二行目の「暗号化されずに保存されるぞ!」という警告は出るが、 [y/N] のプロンプトは出ずにそのままログインが成功する。

echo "$GCLOUD_ACCESS_TOKEN" | docker login -u 'oauth2accesstoken' --password-stdin 'https://gcr.io'

-u 'oauth2accesstoken' は現状では固定の様だ。それ以外の値を指定するとログインに失敗する。

あと gcloud docker と打つだけで生成される ~/.docker/config.json には "email": "not@val.id" とあったので試しに -e 'not@val.id' としてみたら

unknown shorthand flag: 'e' in -e
See 'docker login --help'.

と怒られる。email の時代は終わりです。

まとめるとこうなる。

gcr-login.sh

#!/usr/bin/env bash

GCLOUD_ACCESS_TOKEN=$(docker container run --rm -v $HOME/.config:/root/.config google/cloud-sdk gcloud auth print-access-token)

HOSTS=('https://gcr.io' 'https://asia.gcr.io' 'https://eu.gcr.io' 'https://us.gcr.io' 'https://staging-k8s.gcr.io')

for HOST in ${HOSTS[@]}; do
    echo "$GCLOUD_ACCESS_TOKEN" | docker login -u 'oauth2accesstoken' --password-stdin "$HOST"
done

とりあえずこれで docker login してしまえば、力技ではあるが一応問題は解決する。

成功例 その2 (正しくラップして credHelpers に指定する)(追記:2018/5/28)

docker login 回りの 公式ドキュメント を読んでいたところ、

Keys specify the registry domain, and values specify the suffix of the program to use (i.e. everything after docker-credential-).

という記述を見付けた。

要するにこれは credHelpersgcloud と指定した場合は docker-credential-gcloud を見に行くということなのではないかと気付いた。

試しに、 .docker/config.jsonauths の記述がなくても動いている Mac での環境で調べたらところ

$ which docker-credential-gcloud
/Users/dnpp/google-cloud-sdk/bin/docker-credential-gcloud

と出てきて、あっこれだわ、っとなった。

幸いシェルスクリプトだったので中を見ると

docker-credential-gcloud

#!/bin/sh
#
# Copyright 2017 Google Inc. All Rights Reserved.
#

# [...] (中略) 実行環境のチェックをして変数に放り込んだり export したりするコードが続いてる。

"${CLOUDSDK_ROOT_DIR}/bin/gcloud" auth docker-helper "$@"

となっていた。

つまり gcloud auth docker-helper "$@" という内容のラッパースクリプトを /opt/bin/docker-credential-gcloud に置けば綺麗に解決するのではないかと思い、やってみたところ見事に解決した。

docker-credential-gcloud

#!/usr/bin/env bash

docker container run --rm -i \
-v $HOME/.config:/root/.config \
google/cloud-sdk \
gcloud auth docker-helper "$@"

これを /opt/bin に放り込んで 755 にしてやって gcloud auth configure-docker してやれば良い。

ポイントは docker -i でコンテナの STDIN にアタッチしてやること。データのやり取りは標準入力経由でされている。

もしかしたら $HOME ではなく /home/core を直接指定してしまった方が、 sudo で実行した場合に /root になってしまって gcloud の認証情報が取れずにコケるのを回避することが出来て便利かもしれない。

echo "https://gcr.io" | docker-credential-gcloud get

上手くいけば上記コマンドでそれっぽい JSON が返ってくるようになり、gcr.io に向けた docker image pull などが成功するようになる。

多分これが一番綺麗なやり方だと思う。

一応環境のメモ

Container Linux by CoreOS alpha (1758.0.0)
Container Linux by CoreOS alpha (1772.0.0)
$ docker --version
Docker version 18.04.0-ce, build 3d479c0

$ docker container run -it --rm google/cloud-sdk gcloud --version
Google Cloud SDK 198.0.0
alpha 2018.04.13
app-engine-go
app-engine-java 1.9.63
app-engine-python 1.9.69
beta 2018.04.13
bigtable
bq 2.0.32
cbt
cloud-datastore-emulator 1.4.1
core 2018.04.13
datalab 20180213
gsutil 4.30
pubsub-emulator 2018.04.13

締め

そもそもの話として gcloud docker コマンドを使うには -- と指定しなくてはいけないのだが、僕にはこれが美しくないと感じてしまう。

引数とオプションを使う側が明確に区別するためのものだが、「先頭に gcloud と付けるだけで docker image pull できますよ!」という触れ込みで触ったのに、一瞬で裏切られたなぁという気持ちは常にあった。

# エラー
$ gcloud docker image pull gcr.io/my-project/my_image:latest
ERROR: (gcloud.docker) unrecognized arguments:
  image
  pull
  gcr.io/my-project/my_image:latest

# -- を付けると通る
$ gcloud docker -- image pull gcr.io/my-project/my_image:latest

あと gcloud docker -- --versiondocker --version で出力が違ったりもする。マジかよって思った。

$ docker container run -it --rm -v $HOME/.config:/root/.config google/cloud-sdk gcloud docker -- --version
Docker version 17.12.0-ce, build c97c6d6

$ docker --version
Docker version 18.04.0-ce, build 3d479c0

中で docker コマンドを別に持ってるのか…。そうか…。

gcloud コマンドはそこそこお行儀が悪いので Docker 内に閉じ込めておきたいという気持ちはかなり強く、 Docker image として配布されているのはとても好印象ではある。

好印象ではあるのだが、これもまたそもそもの問題として google/cloud-sdk の Docker image が 1.65 GB なので、細い回線と細いマシンで docker pull したくないという気持ちも強い。というか Docker image の容量に関してはもうちょっと節約できるやろ、と毎回思っている。

要するに gcloud は邪悪。

5/13 くらいに書き始めたものの、途中で仕事が忙しくなったり、事実関係の調査とか検証を重ねていった結果時間を食われてしまい、出すまで遂に一週間以上掛かってしまった。難しいですね。

今回調べられてない以下の実装について後で調べておきたいなという気持ちだけ残しておきたい。