2019/12/30
- 7
章 あたり。読み進むごとに、この記事を更新していきます。だいたい休みの日にビール飲みながら書いている🍺
Kubernetes Up and Running
Kubernetes: Up and Running, 2nd Edition
- 「なにこれ、わかりやすっ」てなったので、これを教科書にしてハンズオンしながら理解していくことに
- 作者はマイクロソフトのブランドン・バーンズさん Githubに、この本でのサンプルコードがある。
Kubernetesの設計思想
いいこと書いてありますが、まず触りたいので、ここの部分はのちほど戻って更新していきます。
- Declarative configuration vs. Imperative configuration
Kubernetesハンズオンの選択肢
- Google Cloud Platform | Google Kubernetes Engine(今回はこれで)
- Amazon Web Service | Elastic Kubernetes Service
- Microsoft Azure | Azure Kubernetes Service
- Minikube
事前準備
Google Cloud SDK をインストール
Mac なので brew
します。 cask
っていうのが新鮮
brew cask install google-cloud-sdk
いろんなメッセージが流れるが、その中で、ここが重要らしい
==> Source [/usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/completion.bash.inc] in your profile to enable shell command completion for gcloud.
==> Source [/usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/path.bash.inc] in your profile to add the Google Cloud SDK command line tools to your $PATH.
これに従い、.bash_profile
に以下を追加して、読み込む . ~/.bash_profile
source /usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/completion.bash.inc
source /usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/path.bash.inc
いちおうバージョンを確認
$ gcloud version
Google Cloud SDK 273.0.0
bq 2.0.50
core 2019.12.06
gsutil 4.46
初期化する
$ gcloud init
ずらずらっとメッセージが出力される。途中でGoogleアカウントとプロジェクトを聞かれるので、作る/選ぶする
デフォルトのゾーンはUS us-west1-a
にしておいた (最初、東京 asia-northeast1
にしたら、なんかのバージョンが古いとかでうまく動かなかった)
$ gcloud config set compute/zone us-west1-a
$ gcloud config list
[compute]
zone = us-west1-a
[core]
account = ****@gmail.com
disable_usage_reporting = True
project = ****-*******-******
Cluster
いざ、クラスタをつくる!
$ gcloud container clusters create kuar-cluster
WARNING: Accessing a Kubernetes Engine cluster requires the kubernetes commandline
client [kubectl]. To install, run
$ gcloud components install kubectl
... 怒られた。kubectl
のコンポーネントを入れないといけない
gcloud components install kubectl
これで、いけた感じ。以下のような、「昔別の入れ方しただろ?」的なメッセージがでてるが、一旦スルーする
There are older versions of Google Cloud Platform tools on your system PATH.
いざ、クラスタをつくる!(二回目)
$ gcloud container clusters create c1
Creating cluster c1 in us-west1-a... Cluster is being health-checked (master is healthy)...done.
Created [https://container.googleapis.com/v1/projects/*-*-*/zones/us-west1-a/clusters/c1].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-west1-a/c1?project=--**
kubeconfig entry generated for c1.
NAME LOCATION MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS
c1 us-west1-a 1.13.11-gke.14 *.*.*.* n1-standard-1 1.13.11-gke.14 3 RUNNING
できた!ここまでくると kubectl
コマンドが使えるようになってた。なるほどこうなるのか。
確認してみる
gcloud container clusters describe c1
TIPS - kubectl
のコマンド補完を効くようにしとくとよい
brew install bash-completion
echo "source <(kubectl completion bash)" >> ~/.bashrc
Component
コンポーネントをみてみる
$ kubectl get componentstatus
NAME STATUS MESSAGE ERROR
controller-manager Healthy ok
etcd-1 Healthy {"health": "true"}
etcd-0 Healthy {"health": "true"}
scheduler Healthy ok
Node
ノードをみてみる
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
gke-c1-default-pool-748a7c69-2l7j Ready <none> 9m29s v1.13.11-gke.14
gke-c1-default-pool-748a7c69-jbtb Ready <none> 9m29s v1.13.11-gke.14
gke-c1-default-pool-748a7c69-nrtt Ready <none> 9m29s v1.13.11-gke.14
-o
使うと、json
や yaml
フォーマットでより詳細な情報が取れる。長すぎるので出力は割愛する
kubectl get nodes -o [ json | yaml ]
TIPS - $HOME/.kube/config
にいろいろなコンフィグがあるので覗いてみると良いかも
ちなみにこの段階だと、Pod
はまだない
$ kubectl get pods
No resources found.
Pod
Pod とは
A Pod represents a collection of application containers and volumes running in the same execution environment. Pods, not containers, are the smallest deployable artifact in a Kubernetes cluster. This means all of the containers in a Pod always land on the same machine.
-
Pod
は同一の実行環境で動く複数のアプリケーションコンテナとボリュームを集めたもの -
Pod
はk8s
クラスタへデプロイできる最小単位(コンテナが最小単位ではない) - つまり、ある
Pod
の中の全てのコンテナは同じマシンにデプロイされる -
Pod
の中のアプリケーションはPod
のIPアドレスやポート(ネットワークネームスペース)やホストネームを共有する。アプリケーション間はIPCで通信可能
k8s
はコンテナを直に扱うのではなく、Pod
という単位で扱うようだ
Pod にアプリケーションをどういれるべきか
- 例えば、
Wordpress
とDB
を同じPod
に入れたりすると、このペアでスケールアウトすることになるから、うまくない - 同一のマシンでないと動かないようなアプリケーション群を(の場合だけ)同じ
Pod
にいれるとよい
Pod のリソースと構成
- マニフェストファイルで定義する(宣言的コンフィグ)
- マニフェストファイルが処理されて
etcd
へ構成が保存される
Pod ハンズオン
まずはコマンドで作る
$ kubectl run kuard --generator=run-pod/v1 --image=gcr.io/kuar-demo/kuard-amd64:blue
pod/kuard created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
kuard 1/1 Running 0 41m
kuard-78d7989464-bgc58 0/1 CrashLoopBackOff 5 4m1s
作成時にタイプミスしたやつが CrashLoopBackOff
で残ってるが気にしない
Podを消す
$ kubectl delete pods/kuard
pod "kuard" deleted
マニフェストファイルから作る
さっきのコマンドライン create-kuard-pod
と等価なマニュフェストファイルがこれ
apiVersion : v1
kind : Pod
metadata :
name : kuard
spec :
containers :
- image : gcr.io/kuar-demo/kuard-amd64:blue
name : kuard
ports :
- containerPort : 8080
name : http
protocol: TCP
読み込む
$ kubectl apply -f kuard-pod.yaml
pod/kuard created
Podの詳細
$ kubectl describe pods kuard
長い出力がでるので割愛
Pod の中へ
ポートフォワードを設定
$ kubectl port-forward kuard 8080:8080
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
Pod のログ
$ kubectl logs kuard
2019/12/15 05:39:28 Starting kuard version: v0.10.0-blue
2019/12/15 05:39:28 **********************************************************************
2019/12/15 05:39:28 * WARNING: This server may expose sensitive
2019/12/15 05:39:28 * and secret information. Be careful.
2019/12/15 05:39:28 **********************************************************************
2019/12/15 05:39:28 Config:
{
"address": ":8080",
"debug": false,
"debug-sitedata-dir": "./sitedata",
"keygen": {
"enable": false,
"exit-code": 0,
"exit-on-complete": false,
"memq-queue": "",
"memq-server": "",
"num-to-gen": 0,
"time-to-run": 0
},
"liveness": {
"fail-next": 0
},
"readiness": {
"fail-next": 0
},
"tls-address": ":8443",
"tls-dir": "/tls"
}
2019/12/15 05:39:28 Could not find certificates to serve TLS
2019/12/15 05:39:28 Serving on HTTP on :8080
2019/12/15 05:41:06 127.0.0.1:55558 GET /
2019/12/15 05:41:06 Loading template for index.html
2019/12/15 05:41:06 127.0.0.1:55558 GET /static/css/bootstrap.min.css
2019/12/15 05:41:07 127.0.0.1:55560 GET /static/css/styles.css
2019/12/15 05:41:07 127.0.0.1:55562 GET /built/bundle.js
2019/12/15 05:41:07 127.0.0.1:55562 GET /favicon.ico
-f
をつけることで、tail -f
みたいなことができる
Pod 内でコマンド実行
$ kubectl exec kuard date
Sun Dec 15 05:49:02 UTC 2019
docker exec
みたいな感じ
$ kubectl exec -it kuard ash
~ $ uname -a
Linux kuard 4.14.138+ #1 SMP Tue Sep 3 02:58:08 PDT 2019 x86_64 Linux
$ cd /tmp
$ touch file.txt
$ echo 'file.txt in kurd /tmp' > file.txt
Pod からファイルをコピー
$ kubectl cp kuard:tmp/file.txt ./file.txt
$ more file.txt
file.txt in kurd /tmp
-it
でインタラクティブシェルを動かすところも docker
と同じ感じ
TIPS - Pod
というの名前の由来だが、Dockerコンテナが くじら をテーマにしているので、くじら(コンテナ)の群れを表す Pod
という名前になったのか?
(The name goes with the whale theme of Docker containers, since a Pod is also a group of whales.)
ヘルスチェック
そもそも、アプリケーションが落ちていたらk8sが再起動してくれるが、生死の確認だけでは足りないだろうから、以下のチェックがある
- Liveness Probe - あるポートへの HTTP Get などアプリ固有のチェック。チェックにFailするとデフォルトの動作はPodをリスタート
- Readiness Probe - アプリケーションが正常に応答できるかをチェック。Failするとロードバラスの対象から外される
HTTP
だけじゃなく tcpSocket
もみるし、 exec
でコマンド結果をチェックすることもできる
TIPS - Liveness Probe vs. Readiness Probe
この二つの違いは微妙な感じのようだ... KubernetesのLiveness ProbeとReadiness Probeの使い分けを考えたけど結局思いつかなかったや
ヘルスチェックを適用する - 5-2-kuard-pod-health.yaml
kubectl apply -f 5-2-kuard-pod-health.yaml
リソース管理
CPUやメモリの使用量をコンテナごとに定義する(Pod毎ではない)
- 最小のリソース
resource
>requests
- リソースの上限
resource
>limits
最小リソースと上限リソースを設定する - 5-4-kaurd-pod-reslim.yaml
apiVersion: v1
kind: Pod
metadata:
name: kuard
spec:
containers:
- image: gcr.io/kuar-demo/kuard-amd64:1
name: kuard
resources:
requests:
cpu: "500m"
memory: "128Mi"
limits:
cpu: "1000m"
memory: "256Mi"
ports:
- containerPort: 8080
name: http
protocol: TCP
ボリューム
- Cache
- Persistent data
- Mounting the host filesystem
- Persistent data using remote disk - NFS, etc.
ホストのファイルシステムをマウント - 5-5-kuard-pod-vol.yaml
NFS マウント - 5-6-kuard-pod-full.yaml
ラベルとアノテーション
- ラベル - Kubernetes オブジェクト(Podなど)につけることのできる
key/value
ペア。グルーピングしたり、見たり、操作するための基礎になる - アノテーション - Kubernetesオブジェクトに対する追加のメタデータ
Pod が増えてくるとラベルでも付けないと大変。ラベルに対して操作(例:ネットワークポリシをあてるなど)することで管理を楽に
ラベルつける --lebels
# alpaca-prod
kubectl run alpaca-prod \
--image=gcr.io/kuar-demo/kuard-amd64:blue \
--replicas=2 \
--labels="ver=1,app=alpaca,env=prod"
# alpaca-test
kubectl run alpaca-test \
--image=gcr.io/kuar-demo/kuard-amd64:green \
--replicas=1 \
--labels="ver=2,app=alpaca,env=test"
# bandicoot-prod
kubectl run bandicoot-prod \
--image=gcr.io/kuar-demo/kuard-amd64:green \
--replicas=2 \
--labels="ver=2,app=bandicoot,env=prod"
# bandicoot-staging
kubectl run bandicoot-staging \
--image=gcr.io/kuar-demo/kuard-amd64:green \
--replicas=1 \
--labels="ver=2,app=bandicoot,env=staging"
ラベルをみる
$ kubectl get deployment --show-labels
NAME READY UP-TO-DATE AVAILABLE AGE LABELS
alpaca-prod 2/2 2 2 30m app=alpaca,env=prod,ver=1
alpaca-test 1/1 1 1 28m app=alpaca,canary=true,env=test,ver=2
bandicoot-prod 2/2 2 2 28m app=bandicoot,env=prod,ver=2
bandicoot-staging 1/1 1 1 28m app=bandicoot,env=staging,ver=2
kuard 0/1 1 0 6d2h run=kuard
-L
でフィルタして見やすく
$ kubectl get deployment -L ver,canary,env,app
NAME READY UP-TO-DATE AVAILABLE AGE VER CANARY ENV APP
alpaca-prod 2/2 2 2 33m 1 prod alpaca
alpaca-test 1/1 1 1 31m 2 true test alpaca
bandicoot-prod 2/2 2 2 31m 2 prod bandicoot
bandicoot-staging 1/1 1 1 31m 2 staging bandicoot
kuard 0/1 1 0 6d2h
ラベルを変える
$ kubectl label deployments bandicoot-staging "canary=true"
deployment.extensions/bandicoot-staging labeled
$ kubectl get deployment -L canary
NAME READY UP-TO-DATE AVAILABLE AGE CANARY
alpaca-prod 2/2 2 2 36m
alpaca-test 1/1 1 1 34m true
bandicoot-prod 2/2 2 2 34m
bandicoot-staging 1/1 1 1 34m true
kuard 0/1 1 0 6d2h
ラベルを消す
ラベル名の後ろに -
をつけると消える
$ kubectl label deployments alpaca-test "canary-"
deployment.extensions/alpaca-test labeled
$ kubectl get deployment -L canary
NAME READY UP-TO-DATE AVAILABLE AGE CANARY
alpaca-prod 2/2 2 2 44m
alpaca-test 1/1 1 1 42m
bandicoot-prod 2/2 2 2 42m
bandicoot-staging 1/1 1 1 42m true
kuard 0/1 1 0 6d2h
ラベルセレクタ --selector="xxx=yyy"
ここまできたら、あとはいろんなコマンドの出力をラベルでフィルタできる
$ kubectl get pods --selector="ver=2" -L ver
NAME READY STATUS RESTARTS AGE VER
alpaca-test-68cc9fdf9d-vthvc 1/1 Running 0 111m 2
bandicoot-prod-585ffc884b-27xnk 1/1 Running 0 111m 2
bandicoot-prod-585ffc884b-vcx6z 1/1 Running 0 111m 2
bandicoot-staging-74dc6b658-mz7fx 1/1 Running 0 111m 2
in
もつかえる
$ kubectl get pods --selector="app in (alpaca, bandicoot)" -L app
NAME READY STATUS RESTARTS AGE APP
alpaca-prod-67474dc478-kxfzq 1/1 Running 0 117m alpaca
alpaca-prod-67474dc478-x5s6m 1/1 Running 0 117m alpaca
alpaca-test-68cc9fdf9d-vthvc 1/1 Running 0 115m alpaca
bandicoot-prod-585ffc884b-27xnk 1/1 Running 0 115m bandicoot
bandicoot-prod-585ffc884b-vcx6z 1/1 Running 0 115m bandicoot
bandicoot-staging-74dc6b658-mz7fx 1/1 Running 0 115m bandicoot
アノテーション
ここは、マイナーそうなので、一回飛ばして、あとで戻ってくる
サービスディスカバリ
Kubernetes はダイナミックすぎて... サービスディスカバリが必要。DNSなど、歴史的に良いサービスディスカバリの仕組みがあるが、キャッシュにより間違ったIPへ接続にいくなど、問題がある。本当のサービスディスカバリが必要。
サービスを作ってみる
kubectl run alpaca-prod \
--image=gcr.io/kuar-demo/kuard-amd64:blue \
--replicas=3 \
--port=8080 \
--labels="ver=1,app=alpaca,env=prod"
kubectl expose deployment alpaca-prod
kubectl run bandicoot-prod \
--image=gcr.io/kuar-demo/kuard-amd64:green \
--replicas=2 \
--port=8080 \
--labels="ver=2,app=bandicoot,env=prod"
kubectl expose deployment bandicoot-prod
$ kubectl get services -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
alpaca-prod ClusterIP 10.35.244.41 <none> 8080/TCP 28m app=alpaca,env=prod,ver=1
bandicoot-prod ClusterIP 10.35.249.225 <none> 8080/TCP 28m app=bandicoot,env=prod,ver=2
kubernetes ClusterIP 10.35.240.1 <none> 443/TCP 12h <none>
## サービスとしてCLUSTER-IPを公開(expose)
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
alpaca-prod-685dd59c7-84sqs 1/1 Running 0 30m
alpaca-prod-685dd59c7-b6ccd 1/1 Running 0 30m
alpaca-prod-685dd59c7-n9tw7 1/1 Running 0 30m
bandicoot-prod-6db7c4fc7c-p47ft 1/1 Running 0 30m
bandicoot-prod-6db7c4fc7c-vf95q 1/1 Running 0 30m
## CLUSTER-IPの背後には複数のPodがあり、ロードバランスされる
that service is assigned a new type of virtual IP called a cluster IP . This is a special IP address the system will load-balance across all of the Pods that are identified by the selector.
クラスタIPという特別なバーチャルIPをつかって、複数のPods間でロードバランスされる
全部消すコマンド
$ kubectl delete deployment,service --all
Service DNS
やっぱりホスト名の方が便利なので、クラスタ内部のIPをホスト名で解決しよう:
alpaca-prod.default.svc.cluster.local. 30 IN A 10.115.245.13
監視の内容を変更する
おお、edit
で直接YAMLを書き換えることができるのか。
$ kubectl edit deployment/alpaca-prod
readinessProbe
を足す
spec:
:
readinessProbe:
failureThreshold: 3
httpGet:
path: /ready
port: 8080
scheme: HTTP
initialDelaySeconds: 2
periodSeconds: 2
successThreshold: 1
timeoutSeconds: 1
エンドポイントの変更をウォッチ
$ kubectl get endpoints alpaca-prod --watch
ClusterIP
はクラスタ内部からしかアクセスできないみたいだ。ここからはクラスタ外部からのアクセスを考える ClusterIP
と NodePorts
は共にクラスタ内部からのアクセス用
NodePorts
を設定する
# service の spec.type を ClusterIP から NodePorts へ書き換え
$ kubectl edit service alpaca-prod
service/alpaca-prod edited
$ kubectl describe service alpaca-prod
Name: alpaca-prod
Namespace: default
Labels: app=alpaca
env=prod
ver=1
Annotations: <none>
Selector: app=alpaca,env=prod,ver=1
Type: NodePort #ClusterIPからNodePortに変わっている
IP: 10.35.248.62
Port: <unset> 8080/TCP
TargetPort: 8080/TCP
NodePort: <unset> 30485/TCP
Endpoints: 10.32.1.14:8080,10.32.2.22:8080,10.32.2.23:8080
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
※ Nodeport
にsshトンネルを張るところでうまくいかない... 一旦飛ばしてあとで帰ってくるか。
LoadBalancer
を設定する
# service の spec.type を NodePorts から LoadBalancer へ書き換え
$ kubectl edit service alpaca-prod
service/alpaca-prod edited
$ kubectl describe service alpaca-prod
Name: alpaca-prod
Namespace: default
Labels: app=alpaca
env=prod
ver=1
Annotations: <none>
Selector: app=alpaca,env=prod,ver=1
Type: LoadBalancer #NodePortからLoadBalancerに変わっている
IP: 10.35.248.62
Port: <unset> 8080/TCP
TargetPort: 8080/TCP
NodePort: <unset> 30485/TCP
Endpoints: 10.32.1.14:8080,10.32.2.22:8080,10.32.2.23:8080
Session Affinity: None
External Traffic Policy: Cluster
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Type 11s service-controller NodePort -> LoadBalancer
Normal EnsuringLoadBalancer 11s service-controller Ensuring load balancer
お、こんどは普通に外部公開されている。http://<LoadBalancer Ingress>:<Port>
で例の画面が見れる
$ kubectl describe endpoints alpaca-prod
Name: alpaca-prod
Namespace: default
Labels: app=alpaca
env=prod
ver=1
Annotations: <none>
Subsets:
Addresses: 10.32.1.14,10.32.2.22,10.32.2.23
NotReadyAddresses: <none>
Ports:
Name Port Protocol
---- ---- --------
<unset> 8080 TCP
Events: <none>
エンドポイントの確認
$ kubectl describe endpoints alpaca-prod
or
$ kubectl get endpoints alpaca-prod
Endpoint IP は deployment を 消したり再作成したりすると変わるのでアプリケーションが参照する場合は注意
TODO - このあたりでServiceというかネットワーク周りを図解した方がいいかもしれない。 ClusterIP
, ExternalIP
, PodIP
, NodeIP
など
手動でのサービスディスカバリ
ラベルを使って目的のものにたどり着くことを手動でのサービスディスカバリと紹介しているようだ
$ kubectl get pods -o wide --show-labels
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
alpaca-prod-685dd59c7-99t54 1/1 Running 0 22m 10.32.1.16 gke-c1-default-pool-748a7c69-2l7j <none> <none> app=alpaca,env=prod,pod-template-hash=685dd59c7,ver=1
alpaca-prod-685dd59c7-dbfph 1/1 Running 0 22m 10.32.2.24 gke-c1-default-pool-748a7c69-nrtt <none> <none> app=alpaca,env=prod,pod-template-hash=685dd59c7,ver=1
alpaca-prod-685dd59c7-dgvxf 1/1 Running 0 22m 10.32.2.25 gke-c1-default-pool-748a7c69-nrtt <none> <none> app=alpaca,env=prod,pod-template-hash=685dd59c7,ver=1
bandicoot-prod-6db7c4fc7c-rwt6d 1/1 Running 0 22m 10.32.1.17 gke-c1-default-pool-748a7c69-2l7j <none> <none> app=bandicoot,env=prod,pod-template-hash=6db7c4fc7c,ver=2
bandicoot-prod-6db7c4fc7c-z5cxs 1/1 Running 0 22m 10.32.0.15 gke-c1-default-pool-748a7c69-jbtb <none> <none> app=bandicoot,env=prod,pod-template-hash=6db7c4fc7c,ver=2
絞り込む
$ kubectl get pods -o wide --selector=app=alpaca,env=prod -L app,env
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES APP ENV
alpaca-prod-685dd59c7-99t54 1/1 Running 0 25m 10.32.1.16 gke-c1-default-pool-748a7c69-2l7j <none> <none> alpaca prod
alpaca-prod-685dd59c7-dbfph 1/1 Running 0 25m 10.32.2.24 gke-c1-default-pool-748a7c69-nrtt <none> <none> alpaca prod
alpaca-prod-685dd59c7-dgvxf 1/1 Running 0 25m 10.32.2.25 gke-c1-default-pool-748a7c69-nrtt <none> <none> alpaca prod
kube-proxy
- Cluster IP はサービス内の Endpoints へトラフィックをロードバランスしている
- その正体は
kube-proxy
。kube-proxy
はクラスタの全てのノードで動いている -
kube-proxy
は apiserverを通して、クラスタ内の新しい service をウォッチしている -
kube-proxy
は カーネルのiptable
ルールを書き換えることでエンドポイントの一つにパケットを送れるようにする - エンドポイントが(落ちたりして)変わったら、
iptable
を書き換える - Cluster IP 自体はサービスが作られるときに apiserver によってアサインされる。ユーザが Cluster IP をしていすることもできる。Cluster IPは変更ができないので削除して再作成となる
TIPS - GUI(ダッシュボード)の説明をあとで確認する
参考