引き続き「Kubernetes完全ガイド」読んでお勉強。
Pod と ReplicaSet と Deployment
Pod
はリソースの最小単位であり、ReplicaSet
は Pod
を管理するリソース。 ReplicaSet
は Pod
が停止すると自動的に再起動したりする。
Deployment
は、 Pod
に含まれるコンテナをバージョンアップする際に、古いコンテナを徐々に減らし、新しいコンテナを徐々に増やすなどの管理をする。
Deployment
- Deploymentを使うと、ワーカーのバージョンアップを行う場合に旧バージョンを徐々に減らし、新バージョンを徐々に増やすローリングアップデートが行える。
Deploymentの作成
以下のようなファイルを作る。
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
が起動しましたね。これによって、 Deployment
は ReplicaSet
と同等の再起動機能を持っていることがわかりました。
では、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-revision
に 0
を指定した場合、一つ前に戻ります。
状況を確認します。
$ 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を定義して起動する。
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のほうはこんな定義になってるはず。
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
に設定してやる。
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を使うと参加している全てのノードの指定ポートで待機するようになるみたいです。やってみましょう。
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.type
にNodePort
を指定。 -
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を取って来てくれるんでしょうかね。
設定はこんな感じで
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
ほーん。