Edited at

Kubernetes勉強 その3(DeploymentとService)

引き続き「Kubernetes完全ガイド」読んでお勉強。

Pod と ReplicaSet と Deployment

Pod はリソースの最小単位であり、ReplicaSetPod を管理するリソース。 ReplicaSetPod が停止すると自動的に再起動したりする。

Deployment は、 Pod に含まれるコンテナをバージョンアップする際に、古いコンテナを徐々に減らし、新しいコンテナを徐々に増やすなどの管理をする。


Deployment


  • Deploymentを使うと、ワーカーのバージョンアップを行う場合に旧バージョンを徐々に減らし、新バージョンを徐々に増やすローリングアップデートが行える。


Deploymentの作成

以下のようなファイルを作る。


sample-deployment.yml

apiVersion: apps/v1

kind: Deployment
metadata:
name: sample-deployment
spec:
replicas: 3
selector:
matchLabels:
app: sample-app
template:
metadata:
labels:
app: sample-app
spec:
containers:
- name: nginx-container
image: nginx:1.12
ports:
- containerPort: 80

コンテナを作成する。

$ kubectl apply -f sample-deployment.yml 

deployment.apps "sample-deployment" created

状況を確認

$ kubectl get pods

NAME READY STATUS RESTARTS AGE
sample-deployment-86b68b4c5b-8v4dg 1/1 Running 0 41s
sample-deployment-86b68b4c5b-cnj45 1/1 Running 0 41s
sample-deployment-86b68b4c5b-pjpln 1/1 Running 0 41s

ポッドが3つできています。試しに一個落としてみます。

$ kubectl delete pod sample-deployment-86b68b4c5b-8v4dg

pod "sample-deployment-86b68b4c5b-8v4dg" deleted

状況を確認してみましょう。

$ kubectl get pods

NAME READY STATUS RESTARTS AGE
sample-deployment-86b68b4c5b-cnj45 1/1 Running 0 2m
sample-deployment-86b68b4c5b-pjpln 1/1 Running 0 2m
sample-deployment-86b68b4c5b-vvcpj 1/1 Running 0 5s

おお、新しく sample-deployment-86b68b4c5b-vvcpj が起動しましたね。これによって、 DeploymentReplicaSet と同等の再起動機能を持っていることがわかりました。

では、nginxを1.12から1.13にアップデートしてみます。

$ kubectl set image deployment sample-deployment nginx-container=nginx:1.13

deployment.apps "sample-deployment" image updated

様子を見てみましょう。

$ kubectl get pods

NAME READY STATUS RESTARTS AGE
sample-deployment-86b68b4c5b-cnj45 1/1 Running 0 7m
sample-deployment-86b68b4c5b-pjpln 1/1 Running 0 7m
sample-deployment-86b68b4c5b-vvcpj 1/1 Terminating 0 5m
sample-deployment-d5b55f699-jhstp 0/1 Pending 0 1s
sample-deployment-d5b55f699-pxpfq 1/1 Running 0 5s

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-deployment-86b68b4c5b-cnj45 1/1 Running 0 7m
sample-deployment-86b68b4c5b-pjpln 1/1 Running 0 7m
sample-deployment-86b68b4c5b-vvcpj 0/1 Terminating 0 5m
sample-deployment-d5b55f699-jhstp 0/1 ContainerCreating 0 3s
sample-deployment-d5b55f699-pxpfq 1/1 Running 0 7s

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-deployment-86b68b4c5b-cnj45 1/1 Terminating 0 7m
sample-deployment-86b68b4c5b-pjpln 1/1 Running 0 7m
sample-deployment-86b68b4c5b-vvcpj 0/1 Terminating 0 5m
sample-deployment-d5b55f699-jhstp 1/1 Running 0 5s
sample-deployment-d5b55f699-k8fvd 0/1 ContainerCreating 0 2s
sample-deployment-d5b55f699-pxpfq 1/1 Running 0 9s

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-deployment-86b68b4c5b-cnj45 0/1 Terminating 0 7m
sample-deployment-86b68b4c5b-pjpln 1/1 Terminating 0 7m
sample-deployment-86b68b4c5b-vvcpj 0/1 Terminating 0 5m
sample-deployment-d5b55f699-jhstp 1/1 Running 0 7s
sample-deployment-d5b55f699-k8fvd 1/1 Running 0 4s
sample-deployment-d5b55f699-pxpfq 1/1 Running 0 11s

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-deployment-86b68b4c5b-cnj45 0/1 Terminating 0 7m
sample-deployment-86b68b4c5b-pjpln 0/1 Terminating 0 7m
sample-deployment-86b68b4c5b-vvcpj 0/1 Terminating 0 5m
sample-deployment-d5b55f699-jhstp 1/1 Running 0 9s
sample-deployment-d5b55f699-k8fvd 1/1 Running 0 6s
sample-deployment-d5b55f699-pxpfq 1/1 Running 0 13s

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-deployment-86b68b4c5b-pjpln 0/1 Terminating 0 7m
sample-deployment-d5b55f699-jhstp 1/1 Running 0 15s
sample-deployment-d5b55f699-k8fvd 1/1 Running 0 12s
sample-deployment-d5b55f699-pxpfq 1/1 Running 0 19s

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-deployment-d5b55f699-jhstp 1/1 Running 0 21s
sample-deployment-d5b55f699-k8fvd 1/1 Running 0 18s
sample-deployment-d5b55f699-pxpfq 1/1 Running 0 25s

おお、新しいコンテナが立ち上がると同時に、古いコンテナが徐々に消えていきます。想定していた通りの動きとはいえ、なかなか感動ものですね。

さて、いまはコマンドでコンテナのバージョンを指定しましたが、ファイルでバージョンを上げた場合の動きを見てみましょう。

ファイルを書き換えます。書き換えるのはnginxのバージョンのみ。

apiVersion: apps/v1

kind: Deployment
metadata:
name: sample-deployment
spec:
replicas: 3
selector:
matchLabels:
app: sample-app
template:
metadata:
labels:
app: sample-app
spec:
containers:
- name: nginx-container
image: nginx:1.13
ports:
- containerPort: 80

続いて、変更を反映します。

まずは、一旦全てのコンテナを削除して、 nginx:1.12 で立ち上げ直しましょう。

$ kubectl delete -f sample-deployment.yml 

deployment.apps "sample-deployment" deleted

$ kubectl apply -f sample-deployment.yml
deployment.apps "sample-deployment" created

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-deployment-86b68b4c5b-drg4l 0/1 ContainerCreating 0 3s
sample-deployment-86b68b4c5b-dwb28 0/1 ContainerCreating 0 3s
sample-deployment-86b68b4c5b-k6vkt 0/1 ContainerCreating 0 3s
$
$
$ vi sample-deployment.yml
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-deployment-86b68b4c5b-drg4l 1/1 Running 0 27s
sample-deployment-86b68b4c5b-dwb28 1/1 Running 0 27s
sample-deployment-86b68b4c5b-k6vkt 1/1 Running 0 27s

ここで、設定ファイルを修正して nginx:1.13 にアップデートします。

apiVersion: apps/v1

kind: Deployment
metadata:
name: sample-deployment
spec:
replicas: 3
selector:
matchLabels:
app: sample-app
template:
metadata:
labels:
app: sample-app
spec:
containers:
- name: nginx-container
image: nginx:1.13
ports:
- containerPort: 80

$ kubectl apply -f sample-deployment.yml 

deployment.apps "sample-deployment" configured

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-deployment-86b68b4c5b-drg4l 1/1 Running 0 1m
sample-deployment-86b68b4c5b-dwb28 1/1 Running 0 1m
sample-deployment-86b68b4c5b-k6vkt 1/1 Running 0 1m
sample-deployment-d5b55f699-rbftp 0/1 ContainerCreating 0 5s

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-deployment-86b68b4c5b-drg4l 1/1 Running 0 1m
sample-deployment-86b68b4c5b-dwb28 1/1 Terminating 0 1m
sample-deployment-86b68b4c5b-k6vkt 1/1 Running 0 1m
sample-deployment-d5b55f699-rbftp 1/1 Running 0 13s
sample-deployment-d5b55f699-xvm78 0/1 Pending 0 2s

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-deployment-86b68b4c5b-drg4l 0/1 Terminating 0 2m
sample-deployment-86b68b4c5b-k6vkt 1/1 Running 0 2m
sample-deployment-d5b55f699-rbftp 1/1 Running 0 49s
sample-deployment-d5b55f699-rwm49 0/1 ContainerCreating 0 13s
sample-deployment-d5b55f699-xvm78 1/1 Running 0 38s

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-deployment-86b68b4c5b-k6vkt 1/1 Terminating 0 2m
sample-deployment-d5b55f699-rbftp 1/1 Running 0 1m
sample-deployment-d5b55f699-rwm49 1/1 Running 0 30s
sample-deployment-d5b55f699-xvm78 1/1 Running 0 55s

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-deployment-d5b55f699-rbftp 1/1 Running 0 1m
sample-deployment-d5b55f699-rwm49 1/1 Running 0 1m
sample-deployment-d5b55f699-xvm78 1/1 Running 0 1m
$


ロールバック

なんらかの理由で、古いバージョンに戻さなくてはならなくなったとします。まあよくある話です。

$ kubectl rollout undo deployment sample-deployment --to-revision 0

deployment.apps "sample-deployment"

--to-revision0 を指定した場合、一つ前に戻ります。

状況を確認します。

$ kubectl get pods

NAME READY STATUS RESTARTS AGE
sample-deployment-86b68b4c5b-zlnnf 0/1 ContainerCreating 0 3s
sample-deployment-d5b55f699-rbftp 1/1 Running 0 12m
sample-deployment-d5b55f699-rwm49 1/1 Running 0 12m
sample-deployment-d5b55f699-xvm78 1/1 Running 0 12m

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-deployment-86b68b4c5b-zlnnf 0/1 ContainerCreating 0 7s
sample-deployment-d5b55f699-rbftp 1/1 Running 0 12m
sample-deployment-d5b55f699-rwm49 1/1 Running 0 12m
sample-deployment-d5b55f699-xvm78 1/1 Running 0 12m

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-deployment-86b68b4c5b-4skwv 1/1 Running 0 6s
sample-deployment-86b68b4c5b-cfmkh 1/1 Running 0 12s
sample-deployment-86b68b4c5b-zlnnf 1/1 Running 0 20s
sample-deployment-d5b55f699-rbftp 1/1 Terminating 0 12m

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
sample-deployment-86b68b4c5b-4skwv 1/1 Running 0 13s
sample-deployment-86b68b4c5b-cfmkh 1/1 Running 0 19s
sample-deployment-86b68b4c5b-zlnnf 1/1 Running 0 27s

新しいコンテナが立ち上がりました。


Serviceによる内部IPアドレス

先ほどのdeploymentに加えて、以下のServiceを定義して起動する。


sample-clusterip.yml

apiVersion: v1

kind: Service
metadata:
name: sample-clusterip
spec:
type: ClusterIP
ports:
- name: "http-port"
protocol: "TCP"
port: 8080
targetPort: 80
selector:
app: sample-app

ちなみに、おさらいしておくと、deploymentのほうはこんな定義になってるはず。


sample-deployment.yml

apiVersion: apps/v1

kind: Deployment
metadata:
name: sample-deployment
spec:
replicas: 3
selector:
matchLabels:
app: sample-app
template:
metadata:
labels:
app: sample-app
spec:
containers:
- name: nginx-container
image: nginx:1.13
ports:
- containerPort: 80

ここで、deplymentのほうで app: sample-app としておいて、serviceの方で、 selector: app: sample-app としているので、serviceは自身の8080番ポートへ届いたパケットを、deploymentの3つのレプリカへ転送することになる。

まず、念のためdeploymentをapply

$ kubectl apply -f sample-deployment.yml

続いて、serviceをapply

$ kubectl apply -f sample-clusterip.yml

これでServiceとDeploymentか起動してるはず。

$ kubectl get pods,services

NAME READY STATUS RESTARTS AGE
pod/sample-deployment-d5b55f699-dpdmz 1/1 Running 0 3m
pod/sample-deployment-d5b55f699-sh452 1/1 Running 0 3m
pod/sample-deployment-d5b55f699-x557b 1/1 Running 0 3m

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 10h
service/sample-clusterip ClusterIP 10.111.247.243 <none> 8080/TCP 8s

443番ポートにも何かサービスが立ち上がってるけど、これはデフォルトで立ち上がってるものだろうか...?

それ以外はpodもserviceも先ほど定義したものが立ち上がっています。

Serviceの状態を確認します。

$ kubectl describe svc sample-clusterip

Name: sample-clusterip
Namespace: default
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"sample-clusterip","namespace":"default"},"spec":{"ports":[{"name":"http-port",...
Selector: app=sample-app
Type: ClusterIP
IP: 10.111.247.243
Port: http-port 8080/TCP
TargetPort: 80/TCP
Endpoints: 172.17.0.4:80,172.17.0.5:80,172.17.0.6:80
Session Affinity: None
Events: <none>

IP がServiceのIPアドレス、 Endpoints がトラフィックを割り振る各コンテナのIPアドレス、 Port はServiceのポート番号、 TargetPort は各コンテナのサービスポートみたい。

各コンテナのhostnameをnginxのwwwrootにコピーして、アクセスしたコンテナがわかるようにします。

$ for PODNAME in `kubectl get pods -l app=sample-app -o jsonpath='{.items[*].metadata.name}'`; do

kubectl exec -it ${PODNAME} -- cp /etc/hostname /usr/share/nginx/html/index.html; done

ClusterIPは外部からはアクセスできないので、一時コンテナを立ち上げてチェック

$ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -s http://10.111.247.243:8080

sample-deployment-d5b55f699-sh452

$ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -s http://10.111.247.243:8080
sample-deployment-d5b55f699-x557b

$ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -s http://10.111.247.243:8080
sample-deployment-d5b55f699-x557b

$ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -s http://10.111.247.243:8080
sample-deployment-d5b55f699-sh452

$ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -s http://10.111.247.243:8080
sample-deployment-d5b55f699-x557b

$ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -s http://10.111.247.243:8080
sample-deployment-d5b55f699-dpdmz

$ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -s http://10.111.247.243:8080

アクセスするごとにコンテナ名が変わっているので、複数コンテナに割り振られているらしい。


名前解決

上記はIPアドレスでアクセスしたが、kubernetesでは内部DNSによって自動的に名前付けが行われるらしい。

ためしに今回定義した sample-clusterip という名前でアクセスしてみる。

$ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -s http://sample-clusterip:8080

sample-deployment-d5b55f699-x557b

おお、何も名前解決のための設定をしていないのに解決できた。アプリケーションはこちらを使って接続先を定義した方が、毎回書き換える必要がなくてよさそうだ。

ただし、先ほどは同じネームスペース内からのアクセスだから sample-clusterip で良かったが、本来はこちらが正確らしい。

$ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -s http://sample-clusterip.default.svc.cluster.local:8080

sample-deployment-d5b55f699-dpdmz

[Serivice名].[Namespace名].svc.cluster.local が正式なFQDNになる。


ExternalIPによるサービス内部公開

ClusterIPでは内部ロードバランサを定義しましたが、これはあくまで内部で使うのものなので、外部からアクセスすることはできない。

けど、Webサーバーなどで外部公開が目的のものは外部に公開できなければならない。この場合はExternalIPを使う。

ExternalIPといっても実態はClusterIPで、ClusterIPに spec.externalIPs にノードのIPアドレスを定義すると使えるようになる。

最初にノードのIPアドレスを調べる。minikubeを使っている場合は、仮想環境のアドレスであってMacのアドレスではないので注意が必要。

$ kubectl cluster-info

Kubernetes master is running at https://192.168.99.100:8443
KubeDNS is running at https://192.168.99.100:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

これをみると、どうやらノードのIPアドレスは 192.168.99.100 のようだ。

このアドレスを新たに作った sample-externalip.yml に設定してやる。


sample-externalip.yml

apiVersion: v1

kind: Service
metadata:
name: sample-externalip
spec:
type: ClusterIP
externalIPs:
- 192.168.99.100
ports:
- name: "http-port"
protocol: "TCP"
port: 8080
targetPort: 80
selector:
app: sample-app

applyする。

$ kubectl apply -f sample-externalip.yml 

状態を見てみよう。

$ kubectl describe svc sample-externalip

Name: sample-externalip
Namespace: default
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"sample-externalip","namespace":"default"},"spec":{"externalIPs":["192.168.99.1...
Selector: app=sample-app
Type: ClusterIP
IP: 10.103.102.209
External IPs: 192.168.99.100
Port: http-port 8080/TCP
TargetPort: 80/TCP
Endpoints: 172.17.0.4:80,172.17.0.5:80,172.17.0.6:80
Session Affinity: None
Events: <none>

External IPs として設定したIPアドレスが割り付けられている。

curlで外部からアクセスしてみよう。

$ curl http://192.168.99.100:8080

sample-deployment-d5b55f699-dpdmz

$ curl http://192.168.99.100:8080
sample-deployment-d5b55f699-sh452

$ curl http://192.168.99.100:8080
sample-deployment-d5b55f699-dpdmz

$ curl http://192.168.99.100:8080
sample-deployment-d5b55f699-x557b

$ curl http://192.168.99.100:8080
sample-deployment-d5b55f699-dpdmz

リクエストが各コンテナに割り振られているのが確認できた。


NodePortによる、全ノードでのListen

ExternalIPを使うと、指定したIPアドレスのノードでListenしますが、NodePortを使うと参加している全てのノードの指定ポートで待機するようになるみたいです。やってみましょう。


sample-nodeport.yml

apiVersion: v1

kind: Service
metadata:
name: sample-nodeport
spec:
type: NodePort
ports:
- name: "http-port"
protocol: "TCP"
port: 8080
targetPort: 80
nodePort: 30080
selector:
app: sample-app

ExternalIPの定義とよく似ているが、違うのは



  • spec.typeNodePort を指定。


  • spec.externalIPs を定義しない。


  • spec.ports.nodePort に公開するポート番号を指定する。

のようです。

さっそくapplyします。

$ kubectl apply -f sample-nodeport.yml

service "sample-nodeport" created

curlでアクセスしてみる。

$ curl http://192.168.99.100:30080

sample-deployment-d5b55f699-dpdmz

ふう。

とりえあず動作が確認できたので削除。

$ kubectl delete -f sample-nodeport.yml

service "sample-nodeport" deleted

$ kubectl delete -f sample-cluster.yaml
service "sample-clusterip" deleted


LoadBalancerによる外部公開

サービスを外部公開する場合、結局これになるんだろうな〜、っていう。いわゆるロードバランサー。

LoadBalancerの実装はKubernetesのノードがどこで動いているかに依存するようです。

まだ試してませんが、AWSであればLoadBalancerを定義するとElasticIPを取って来てくれるんでしょうかね。

設定はこんな感じで


sample-loadbalancer.yml

apiVersion: v1

kind: Service
metadata:
name: sample-lb
spec:
type: LoadBalancer
loadBalancerIP: 192.168.99.100
ports:
- name: "http-port"
protocol: "TCP"
port: 8080
targetPort: 80
nodePort: 30082
selector:
app: sample-app

loadbalancerIPは定義しなくてもいいけど、普通は外部公開するサービスであればIPアドレスは固定になるので定義した。

定義しない場合は自動的に割り振られる。

起動します。

$ kubectl apply -f sample-loadbalancer.yml 

service "sample-lb" created

サービスの様子を確認します。

$ kubectl get services

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 14h
sample-lb LoadBalancer 10.106.15.138 <pending> 8080:30082/TCP 16m

あれ、sample-lbのEXTERNAL-IPがpendingのまま。

このままではどうすればいいかわからないので、minikubeコマンドでURL取得。

$ minikube service sample-lb --url

http://192.168.99.100:30082

とうことで、curlでアクセス。

$ curl http://192.168.99.100:30082

sample-deployment-d5b55f699-sh452

$ curl http://192.168.99.100:30082
sample-deployment-d5b55f699-x557b

ほーん。