はじめに
この記事はZOZOテクノロジーズ #5 Advent Calendar 2019の21日目の記事となります。
昨日は、@yskur さんのPMがエンジニアと一緒にER図ランチをしている話でした。
今年は全部で5つのAdvent Calendarが公開されています。
ZOZOテクノロジーズ #1 Advent Calendar 2019
ZOZOテクノロジーズ #2 Advent Calendar 2019
ZOZOテクノロジーズ #3 Advent Calendar 2019
ZOZOテクノロジーズ #4 Advent Calendar 2019
ZOZOテクノロジーズ #5 Advent Calendar 2019
どんな人向けの記事か
- Containerについて勉強中
- Kubernetesについて勉強中
- Kubernetesに自分でPodをDeployした経験があまり無い人
概要
GKE上に検証用としてgcloudコマンドが叩けるPodが必要になったので作成した際に、思った以上に詰まる点が多かったので備忘録として記載します。
私はKubernetesをはじめとしたContainer技術にはあまり縁が無く学習中なので、非常に簡単なところから詰まりました。
この記事がどこかの誰かの助けになれば幸いです。
1. 誤った目算
GKE上にてgcloudコマンドを実行するPodをDeployする際に一番最初に思いついたフローが下記となります。
- ローカルにUbuntuの公式Containerをpull
- Ubuntu Containerを起動してgcloudコマンドをInstall
- 作成したContainerをImage化する
- 作成したImageでGKE上でrunして動作確認
ローカルでContainerを検証した際と同じフローで考えていました。
しかし、Image作成を進めていたところ社内のKubernetes先生から下記条件を与えられました。
- Productionに向けて経験を積むためにもDockerfileでbuildしたContainerを使う
- GKEにて利用するContainerなのでGoogle Container Registry(以下、GCR)を使う
- KubernetesへのデプロイはDeploymentリソースのManifestファイルを作成して行う
この時指摘を受けたのですが、自前のImageを手で作ってデプロイするのはあまり適切ではないとの事です。
確かに『どういった手順で作成されたContainerなのか他メンバーが判断しにくい』ので運用しにくいですね。
2. Dockerfileを作成
Dockerfileを作成するにあたって、当初の予定通りUbuntuの公式イメージをベースに考えていました。
しかし、Ubuntuの公式イメージにgcloudコマンドのインストールを試みたところ必須パッケージ等が色々と不足しておりスムーズに作成できません。
そこで、Googleが公開しているcloud-sdk-dockerを利用する事にしました。
また、今回はgcloudコマンドにてサービスアカウントを利用するためにサービスアカウントを認証させています。
ROM debian:buster
ARG CLOUD_SDK_VERSION=272.0.0
ENV CLOUD_SDK_VERSION=$CLOUD_SDK_VERSION
ENV PATH "$PATH:/opt/google-cloud-sdk/bin/"
RUN apt-get -qqy update && apt-get install -qqy \
curl \
gcc \
python-dev \
python-pip \
apt-transport-https \
lsb-release \
openssh-client \
git \
make \
gnupg && \
pip install -U crcmod && \
echo 'deb http://deb.debian.org/debian/ sid main' >> /etc/apt/sources.list && \
export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)" && \
echo "deb https://packages.cloud.google.com/apt $CLOUD_SDK_REPO main" > /etc/apt/sources.list.d/google-cloud-sdk.list && \
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - && \
apt-get update && \
apt-get install -y google-cloud-sdk=${CLOUD_SDK_VERSION}-0 \
google-cloud-sdk-app-engine-python=${CLOUD_SDK_VERSION}-0 \
google-cloud-sdk-app-engine-python-extras=${CLOUD_SDK_VERSION}-0 \
google-cloud-sdk-app-engine-java=${CLOUD_SDK_VERSION}-0 \
google-cloud-sdk-app-engine-go=${CLOUD_SDK_VERSION}-0 \
google-cloud-sdk-datalab=${CLOUD_SDK_VERSION}-0 \
google-cloud-sdk-datastore-emulator=${CLOUD_SDK_VERSION}-0 \
google-cloud-sdk-pubsub-emulator=${CLOUD_SDK_VERSION}-0 \
google-cloud-sdk-bigtable-emulator=${CLOUD_SDK_VERSION}-0 \
google-cloud-sdk-cbt=${CLOUD_SDK_VERSION}-0 && \
gcloud --version
VOLUME ["/root/.config"]
COPY service_account.key .
RUN gcloud auth activate-service-account --key-file service_account.key
3. docker build
作成したDockerfileを元にbuild作業を行います。
Image名ですが、GCRへのpushを行う場合には下記要件に従った名前にする必要があります。
gcr.io/[PROJECT-ID]/[IMAGE]:[TAG]
そこで今回はgcloud-devというImage名で作成してみました。
$ docker build -t gcr.io/[PROJECT-ID]/gcloud-dev:v1 .
[+] Building 256.9s (9/9) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 1.64kB 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/debian:buster 2.6s
=> CACHED [1/4] FROM docker.io/library/debian:buster@sha256:79f0b1682af1a6a29ff63182c8103027f4de98b22d8fb50040e9c4bb13e3de78 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 41B 0.0s
=> [2/4] RUN apt-get -qqy update && apt-get install -qqy curl gcc python-dev python-pip apt-transport-https lsb-release openssh-client git make 214.8s
=> [3/4] COPY service_account.key . 0.1s
=> [4/4] RUN gcloud auth activate-service-account --key-file service_account.key 5.5s
=> exporting to image 33.7s
=> => exporting layers 33.7s
=> => writing image sha256:hogefugahogefugahogefugahogefugahogefugahogefugahogefuga 0.0s
=> => naming to gcr.io/[PROJECT-ID]/gcloud-dev:v1
作成したものを確認してみます。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
gcr.io/[PROJECT-ID]/gcloud-dev v1 hogehogeho 5 minutes ago 1.71GB
4. GCRへのpush
名前を適切に定義しているのでGCRへのpushはコマンド一つで可能です。
$ gcloud docker -- push gcr.io/[PROJECT-ID]/gcloud-dev:v1
WARNING: `gcloud docker` will not be supported for Docker client versions above 18.03.
As an alternative, 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: `gcloud docker
--verbosity=error -- pull gcr.io/project-id/my-image`.
See: https://cloud.google.com/container-registry/docs/support/deprecation-notices#gcloud-docker
The push refers to repository [gcr.io/[PROJECT-ID]/gcloud-dev]
cab060f85f59: Pushed
d6d840cf61fa: Pushed
b26a6aae8eca: Pushed
f2b4f0674ba3: Layer already exists
v1: digest: sha256:hogehogehogehogehogehogehogehogehogehogehogehogehogehoge size: 1158
Updates are available for some Cloud SDK components. To install them,
please run:
$ gcloud components updat
GCPのWebUIから見ても正常にpushされている事がわかります。
cliにも出ていますがver18.03以上のDockerクライアントではgcloud dockerコマンドは動作保証されておりません。(2019年12月現在)。ver18.03以上のDockerクライアントを利用する場合は記載してあるURL先にある対応が必要です。
5. kubectl run を使ったPodの動作確認
作成したContainerImageからPodを作成して正常に動作するかを確認します。
$ kubectl run gcloud-dev --image=gcr.io/[PROJECT-ID]/gcloud-dev:v1
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/gcloud-dev created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
gcloud-dev-795d47546c-vcw9d 0/1 CrashLoopBackOff 6 11m
_人人人人人人人人人人人人_
> CrashLoopBackOff <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄
6. 原因調査
Podの詳細情報を見るためにdescribeコマンドを叩いてみます。
$ kubectl describe pod gcloud-dev-795d47546c-vcw9d
Name: gcloud-dev-795d47546c-vcw9d
~省略~
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 24m default-scheduler Successfully assigned default/gcloud-dev-795d47546c-vcw9d to gke-gke-spanner-dev--gke-spanner-dev--59f9fd2a-mm2d
Normal Pulling 24m kubelet, gke-gke-spanner-dev--gke-spanner-dev--59f9fd2a-mm2d Pulling image "gcr.io/[PROJECT-ID]/gcloud-dev:v1"
Normal Pulled 22m kubelet, gke-gke-spanner-dev--gke-spanner-dev--59f9fd2a-mm2d Successfully pulled image "gcr.io/[PROJECT-ID]/gcloud-dev:v1"
Normal Created 21m (x5 over 22m) kubelet, gke-gke-spanner-dev--gke-spanner-dev--59f9fd2a-mm2d Created container gcloud-dev
Normal Started 21m (x5 over 22m) kubelet, gke-gke-spanner-dev--gke-spanner-dev--59f9fd2a-mm2d Started container gcloud-dev
Normal Pulled 21m (x4 over 22m) kubelet, gke-gke-spanner-dev--gke-spanner-dev--59f9fd2a-mm2d Container image "gcr.io/[PROJECT-ID]/gcloud-dev:v1" already present on machine
Warning BackOff 4m5s (x89 over 22m) kubelet, gke-gke-spanner-dev--gke-spanner-dev--59f9fd2a-mm2d Back-off restarting failed container
特に明示的なerrorを吐かずにrestartが走っています。
CrashLoopBackOffになる原因調べてみたところ、『Pod内Containerのプロセス終了を検知して再起動している』といった場合もCrashLoopBackOffになるとの事でした。
勘の良い方、Containerに詳しい方ならお気づきですね。
今回作成したContainerではforegroundで動かすようなプロセスを作成していませんでした。
GKEに限らずKubernetesではセルフヒーリングの機能があります。
これはPodがCrash等の理由でdown時に適切な数に戻るように自動で再作成してくれる機能です。
今回はPod内のプロセスが終了(そもそも無い)したのでセルフヒーリングによるrestartを繰り返そうとした結果CrashLoopBackOffとなったみたいです。
6. 解決策
今回はContainer内でgcloudコマンドを使う事が目的なので、適当にNginxを起動してContainerが異常終了する事を防ぎます。
解決方法としてImage作成時のDockerfileにて下記2つの処理を追加しました。
- NginxのInstall
- Container起動時にNginxのプロセスを起動
作成したものが以下となります。
ROM debian:buster
ARG CLOUD_SDK_VERSION=272.0.0
ENV CLOUD_SDK_VERSION=$CLOUD_SDK_VERSION
ENV PATH "$PATH:/opt/google-cloud-sdk/bin/"
RUN apt-get -qqy update && apt-get install -qqy \
curl \
gcc \
python-dev \
python-pip \
apt-transport-https \
lsb-release \
openssh-client \
git \
make \
gnupg && \
pip install -U crcmod && \
echo 'deb http://deb.debian.org/debian/ sid main' >> /etc/apt/sources.list && \
export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)" && \
echo "deb https://packages.cloud.google.com/apt $CLOUD_SDK_REPO main" > /etc/apt/sources.list.d/google-cloud-sdk.list && \
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - && \
apt-get update && \
apt-get install -y google-cloud-sdk=${CLOUD_SDK_VERSION}-0 \
google-cloud-sdk-app-engine-python=${CLOUD_SDK_VERSION}-0 \
google-cloud-sdk-app-engine-python-extras=${CLOUD_SDK_VERSION}-0 \
google-cloud-sdk-app-engine-java=${CLOUD_SDK_VERSION}-0 \
google-cloud-sdk-app-engine-go=${CLOUD_SDK_VERSION}-0 \
google-cloud-sdk-datalab=${CLOUD_SDK_VERSION}-0 \
google-cloud-sdk-datastore-emulator=${CLOUD_SDK_VERSION}-0 \
google-cloud-sdk-pubsub-emulator=${CLOUD_SDK_VERSION}-0 \
google-cloud-sdk-bigtable-emulator=${CLOUD_SDK_VERSION}-0 \
google-cloud-sdk-cbt=${CLOUD_SDK_VERSION}-0 \
nginx && \
gcloud --version
VOLUME ["/root/.config"]
COPY service_account.key .
RUN gcloud auth activate-service-account --key-file service_account.key
CMD ["/usr/sbin/nginx", "-g", "daemon off;"]
こちらで『gcr.io/[PROJECT-ID]/gcloud-dev:v2』としてGCRにpushしました。
7. kubectl run で改めてPodの動作確認
作成したv2にて改めてPodを動かしてみます。
$ kubectl run gcloud-dev --image=gcr.io/[PROJECT-ID]/gcloud-dev:v2
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/gcloud-dev created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
gcloud-dev-6bf59ffc9b-r77zd 1/1 Running 0 8s
$ kubectl exec -it gcloud-dev-6bf59ffc9b-r77zd /bin/bash
root@gcloud-dev-6bf59ffc9b-r77zd:/# gcloud --version
Google Cloud SDK 272.0.0
alpha 2019.11.16
app-engine-go
app-engine-java 1.9.77
app-engine-python 1.9.87
app-engine-python-extras 1.9.87
beta 2019.11.16
bigtable
bq 2.0.50
cbt
cloud-datastore-emulator 2.1.0
core 2019.11.16
datalab 20190610
gsutil 4.46
kubectl 2019.11.16
pubsub-emulator 0.1.0
root@gcloud-dev-6bf59ffc9b-r77zd:/#
正常にrunningになりましたね。
gcloudコマンドも動いてそうで一安心です。
8. DeploymentリソースのManifestファイルを作成
Containerの動作確認はできたのでDeploymentリソースのManifestファイルを作成します。
今回はServiceリソースの作成は検証に必要ないのでPod作成のみとなります。
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-gcloud-dev
spec:
selector:
matchLabels:
app: gcloud-dev
replicas: 2
template:
metadata:
labels:
app: gcloud-dev
spec:
containers:
- name: gcloud-container
image: gcr.io/[PROJECT-ID]/gcloud-dev:v2
ports:
- containerPort: 80
作成したDeploymentリソースを使ってGKE上にデプロイしてみます。
$ kubectl create -f deployment-gcloud-dev.yaml
deployment.apps/deployment-gcloud-dev created
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
deployment-gcloud-dev 2/2 2 2 14s
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
deployment-gcloud-dev-54f87c5f77-564t8 1/1 Running 0 19s
deployment-gcloud-dev-54f87c5f77-5gw7w 1/1 Running 0 19s
正常にデプロイできました。
これにて完了となります。
雑感
冒頭に記載した通り、私自身はKubernetesをはじめとしたContainer技術を勉強中で本を読みながら環境を作った程度だったので非常に細かな躓きが多くなってしまいました。
本の通りに動かすのではなく、実際に自分で要件を持って進めたことで細部を知ることができる良い機会となりました。
今回の記事では要件に従い、『どのように考えて』『どのように失敗して』『どのように解決したか』を順を追って記載しました。
まだまだContainer/Kubernetesが広範囲で利用されているとは言い難く勉強中の方がたくさん居ると思います。
そういった方達に最初の1歩の助けとしてカジュアルに読んで頂ければ幸いです。
Container/Kubernetes勉強中の皆様、長い道程ですが一緒にがんばりましょう!