GCPUG Tokyo June 2018
抽選に初当選したので行ってみた。メモ。
はじめに
GCPUGについて紹介
How to utilize GKE for QA environment / @masudak (mercari)
- mercariでは、QA環境の中で1年近く前からGKEつかってるらしい。今日はそのお話。
- 発表者:@masudak
- 開発フロー
- PMが仕様をJIRAに書く
- エンジニア・デザイナーがローカルで実装
- レビュー/
- QA環境でQAエンジニアがテスト
- QA環境
- QAエンジニア/エンジニアがQAする環境
- 共通のDB、検索データをもつ
- API/Frontend/Admin/Webなどが動く
- これらは独立してて、他の人に影響を与えないでテストできる
- 要件
- 自分専用/WebUIでデプロイ/スケール/可能な限り本番構成反映
- 2017春
- 2017後半
- まとめ
- 開発生産性も品質もまだ追求できる
- たった1つのVM時代(virtualenvとか使って環境分離??)から2年でここまできた
- Q&A
GCPの新機能の話もあったけど、App Engineはちゃんとつかっていないので、メモはなし ;p
感想
- SETというroleを作り、QAにもちゃんとコストかけて改善しているのは良いというか羨ましいですね。
- CircleCIのながでごにょごにょの部分が今ひとつ何やってるかわからず。PRごとにReplicaSet作ってデプロイしているのかな。そのうち資料が公開されたら改めて確認する。
- ここでもCRDだった。やはりCRD化(k8s Native化)が流れなの?
- マイクロサービス化に取り組んでいるのここ1年ぐらいなんですね。もっと前からやっているのかと勝手に思ってた。
- たった1つのVM時代とはどんな感じでQA環境として使っていたのか気になった。
- 開発はローカルでやっているみたいだけど、やはり開発はローカルでやるのが普通なのかな。GKE上とかでやらないのか気になった。
kubeadmで構築した環境のkube-apiserverを再起動する
kube-apiserverを設定変更して再起動したくなったのだけど、 kubeadmでデプロイした場合どうするのか知らなかったので調べた結果をメモ。
まあ、全く同じ質問がstack overflowにあったので、それを読めば良いです。
まとめると下記。
- kubeadmはkube-apiserverをstatic Podとしてデプロイする
- static Podとは
- そのため、kube-apiserverを再起動したい場合、kube-apiserverが動いているホストに ログインしてdockerコマンドでコンテナを止める
- するとkubeletが再起動してくれる
ちなみに、kube-apiserverのmanifestはcentosの場合、/etc/kubernetes/manifests
にあるので、設定を変更する場合はこれを修正する。
(ちゃんと確認していないけど、設定変更したらkubeletがそれを検知してくれてコンテナ再起動するかも --> 今度調べる)
参考
minikubeでheapsterを使う
minikubeの環境でHPA試してみようかなと思って、とりあえずheapsterが動いているか確認したらいなかったので動かしてみました。
とりあえずminikube環境で、heapsterが動いているか確認。
$ kubectl get pod --namespace=kube-system NAME READY STATUS RESTARTS AGE kube-addon-manager-minikube 1/1 Running 18 68d kube-dns-54cccfbdf8-dwv75 3/3 Running 54 68d kubernetes-dashboard-77d8b98585-b4fhq 1/1 Running 18 68d storage-provisioner 1/1 Running 18 68d
ないですね。 とりあえず、グーグルさんに聞いてみたら、minikubeはaddonのシステムがあって、heapsterもアドオンとして実装されているので、有効にすれば良いらしい。
手元のminikube にあるaddonのリストを見てみる。
$ minikube addons list - addon-manager: enabled - coredns: disabled - dashboard: enabled - default-storageclass: enabled - efk: disabled - freshpod: disabled - heapster: disabled - ingress: disabled - kube-dns: enabled - registry: disabled - registry-creds: disabled - storage-provisioner: enabled
heapsterはdisabledになってますね。有効化。
$ minikube addons enable heapster heapster was successfully enabled
kubectlでheapsterがデプロイされたか確認。
kubectl get pod --namespace=kube-system NAME READY STATUS RESTARTS AGE heapster-nbt68 1/1 Running 0 3s influxdb-grafana-6zm66 2/2 Running 0 3s kube-addon-manager-minikube 1/1 Running 18 68d kube-dns-54cccfbdf8-dwv75 3/3 Running 54 68d kubernetes-dashboard-77d8b98585-b4fhq 1/1 Running 18 68d storage-provisioner 1/1 Running 18 68d
すばらしい。 とても簡単にheapsterがデプロイできました。
[参考]
DNSについて (続き)
kubernetesのdocumentのDNS for Services and Podsでは、次のような記述がある。
Pod’s hostname and subdomain fields
Currently when a pod is created, its hostname is the Pod’s metadata.name value.
The Pod spec has an optional hostname field, which can be used to specify the Pod’s hostname. When specified, it takes precedence over the Pod’s name to be the hostname of the pod. For example, given a Pod with hostname set to “my-host”, the Pod will have its hostname set to “my-host”.
The Pod spec also has an optional subdomain field which can be used to specify its subdomain. For example, a Pod with hostname set to “foo”, and subdomain set to “bar”, in namespace “my-namespace”, will have the fully qualified domain name (FQDN) “foo.bar.my-namespace.svc.cluster.local”.
一例として、headlessサービスでhostname、subdomainの両方を設定しているものが書いてあるけど、普通のサービスだったらとか、hostname指定しなかった場合とかの挙動が分からなかったので、試してみた。
先に結論から言うと、headlessサービスでhostonameとsubdomainの両方の設定をしてある必要がありました。
以下、メモ。
普通のサービス + hostname指定 + subdomain指定
マニフェストは下記。
$ cat normal_hostname_subdomain.yaml apiVersion: v1 kind: Service metadata: name: my-service spec: selector: name: busybox ports: - name: foo port: 1234 targetPort: 5678 --- apiVersion: v1 kind: Pod metadata: name: busybox1 labels: name: busybox spec: hostname: busybox-1 subdomain: my-service containers: - name: busybox image: busybox command: - sleep - "3600" --- apiVersion: v1 kind: Pod metadata: name: busybox2 labels: name: busybox spec: hostname: busybox-2 subdomain: my-service containers: - name: busybox image: busybox command: - sleep - "3600"
デプロイ。
$ kubectl apply -f normal_hostname_subdomain.yaml service "my-service" created pod "busybox1" created pod "busybox2" created $ kubectl get pod NAME READY STATUS RESTARTS AGE busybox1 1/1 Running 0 54s busybox2 1/1 Running 0 54s $ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 12d my-service ClusterIP 10.101.236.136 <none> 1234/TCP 1m
同じクラスタ内の別Podで名前解決してみる。
$ kubectl run -it --rm --restart=Never --image=busybox nslookup -- /bin/sh If you don't see a command prompt, try pressing enter. / # nslookup my-service Server: 10.96.0.10 Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local Name: my-service Address 1: 10.101.236.136 my-service.default.svc.cluster.local / # nslookup busybox1.my-service Server: 10.96.0.10 Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local nslookup: can't resolve 'busybox1.my-service' / # nslookup busybox-1.my-service Server: 10.96.0.10 Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local nslookup: can't resolve 'busybox-1.my-service'
ということで、名前解決はできません。
headlessサービス + hostname指定なし + subdomain指定
利用したマニフェスト。
$ cat headless_no_subdomain.yaml File Edit Options Buffers Tools Text Help apiVersion: v1 kind: Service metadata: name: my-service spec: selector: name: busybox clusterIP: None ports: - name: foo port: 1234 targetPort: 5678 --- apiVersion: v1 kind: Pod metadata: name: busybox1 labels: name: busybox spec: subdomain: my-service containers: - name: busybox image: busybox command: - sleep - "3600" --- apiVersion: v1 kind: Pod metadata: name: busybox2 labels: name: busybox spec: subdomain: my-service containers: - name: busybox image: busybox command: - sleep - "3600"
同じように、デプロイして別のPodから名前解決をしてみる。
$ kubectl apply -f headless_no_subdomain.yaml service "my-service" created pod "busybox1" created pod "busybox2" created $ kubectl get pod NAME READY STATUS RESTARTS AGE busybox1 1/1 Running 0 17s busybox2 1/1 Running 0 17s $ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 12d my-service ClusterIP None <none> 1234/TCP 22s $ kubectl run -it --rm --restart=Never --image=busybox nslookup -- /bin/sh If you don't see a command prompt, try pressing enter. / # nslookup my-service Server: 10.96.0.10 Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local Name: my-service Address 1: 172.17.0.4 Address 2: 172.17.0.5 / # nslookup busybox1.my-service Server: 10.96.0.10 Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local nslookup: can't resolve 'busybox1.my-service'
ということで、busybox1.my-serviceは名前解決できず。 同様に、hostnameを指定して、subdomainを指定しない場合もだめ。hostnameもsubdomainも指定しない場合もだめでした。
以上より、kubernetesのドキュメントにあるように*1名前解決可能にする条件は以下となる。
- headlessサービスであること
- Podのhostnameとsubdomainを設定すること
DNSについて
DNS for Services and Podsに説明が書いてあるけど、試してみないと今一つわからなかったのでメモを残しておく。
Pod + Service
まずは、普通にendpointとなるPod2つと普通のサービスの構成で色々試してみる。環境はminikube。 マニフェストファイルは下記。
$ cat pod-srv.yaml apiVersion: v1 kind: Service metadata: name: my-service spec: selector: name: busybox ports: - name: foo port: 1234 targetPort: 5678 --- apiVersion: v1 kind: Pod metadata: name: busybox1 labels: name: busybox spec: containers: - name: busybox image: busybox command: - sleep - "3600" --- apiVersion: v1 kind: Pod metadata: name: busybox2 labels: name: busybox spec: containers: - name: busybox image: busybox command: - sleep - "3600"
このマニフェストでデプロイ。
$ kubectl apply -f stateless-normal-srv.yaml service "my-service" created pod "busybox1" created pod "busybox2" created
名前解決してみるために、適当なPodを作る。
$ kubectl run -it --rm --restart=NEVER --image=centos dns-test -- /bin/sh If you don't see a command prompt, try pressing enter. # yum install -y bind-utils
まずはサービス名を名前解決。
# nslookup my-service Server: 10.96.0.10 Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local Name: my-service Address 1: 10.111.178.2 my-service.default.svc.cluster.local
Aレコードは10.111.178.2 my-service.default.svc.cluster.local
でしょう。
次にSRVを確認してみる。
ドキュメントによると、SRVレコードは_<named-port._<protocol>.my-service
なので、次のコマンドを実行。
# nslookup -q=SRV _foo._tcp.my-service Server: 10.96.0.10 Address: 10.96.0.10#53 _foo._tcp.my-service.default.svc.cluster.local service = 10 100 1234 my-service.default.svc.cluster.local.
service = <Priority> <Weight> <Port> <Target>
のはずなので、Portが1234でマニフェスト通りとなってることが確認出来る。
Pod + Headless Service
次はHeadless Serviceにして同じことを確認してみる。マニフェストは下記。
$ cat pod-headless-srv.yaml apiVersion: v1 kind: Service metadata: name: my-service spec: selector: name: busybox clusterIP: None ports: - name: foo port: 1234 targetPort: 5678 --- apiVersion: v1 kind: Pod metadata: name: busybox1 labels: name: busybox spec: containers: - name: busybox image: busybox command: - sleep - "3600" --- apiVersion: v1 kind: Pod metadata: name: busybox2 labels: name: busybox spec: containers: - name: busybox image: busybox command: - sleep - "3600"
このマニフェストをデプロイして、先程作成したCentOSのコンテナで名前解決してみる。
# nslookup my-service Server: 10.96.0.10 Address: 10.96.0.10#53 Name: my-service.default.svc.cluster.local Address: 172.17.0.4 Name: my-service.default.svc.cluster.local Address: 172.17.0.5
先程とは異なり、サービス名で名前解決すると、サービスのendpointとなっているPodのIPが返ってくる。これらがAレコードとして登録されているはず。 次に先程と同様にSRVレコードを確認してみる。
# nslookup -q=SRV _foo._tcp.my-service Server: 10.96.0.10 Address: 10.96.0.10#53 _foo._tcp.my-service.default.svc.cluster.local service = 10 50 5678 3237653666313939.my-service.default.svc.cluster.local. _foo._tcp.my-service.default.svc.cluster.local service = 10 50 5678 3139376166613530.my-service.default.svc.cluster.local.
各Podに適当なサブドメインが割り当てられてる。kube-dnsが勝手に付ける模様。そして、PortもServiceのPortではなく、Serviceで定義したtargetPortの値になってますね。まあ、これはPodのFQDNが各々返ってきてるので当然ですね。
ついでにPodのFQDNで名前解決してみる。
# nslookup 3237653666313939.my-service.default.svc.cluster.local Server: 10.96.0.10 Address: 10.96.0.10#53 Name: 3237653666313939.my-service.default.svc.cluster.local Address: 172.17.0.4 # nslookup 3139376166613530.my-service.default.svc.cluster.local Server: 10.96.0.10 Address: 10.96.0.10#53 Name: 3139376166613530.my-service.default.svc.cluster.local Address: 172.17.0.5
ちゃんとPodのIPが返ってきますね。
kubectlコマンドの補完を有効化する
kubectlコマンドはサブコマンドがあったり、オプションがあったりで覚えるのも入力するのも面倒。bash/zsh向けには補完する機能があるので、有効化してみた。Fedora27/bashの環境でのメモ。
$ cat /etc/redhat-release Fedora release 27 (Twenty Seven) $ echo $SHELL /bin/bash
まず、bash-completionパッケージが必要なのでインストール
$ sudo dnf install bash-completion
で、kubectl completionで補完する設定を吐き出してくれるので適当なファイルに吐き出して、bash_profileの中でsourceで読み込んであげればOK*1。
$ kubectl completion bash > ~/.kube/completion.bash.inc $ printf " > # kubectl shell completion > source '$HOME/.kube/completion.bash.inc' > " >> $HOME/.bash_profile $ source $HOEM/.bash?profile
これでkubectlの補完が有効になるはず。
minikue
やopenstack
コマンドも同じようにして補完を有効にできますね。
*1:kubectl completion -hとかでやり方は確認可能
ConfigMapの使い方
Kubernetesを使う時、アプリの設定とかをコンテナイメージに埋め込むのではなく実行時に設定したい場合、ConfigMapを使う。
ConfigMapはkey-valueで情報を保持しているんだけど、これがなかなか便利で、ファイルから作る*1とkey: ファイル名
、value: ファイルの内容
みたいに勝手にやってくれる*2。ディレクトリから作る*3と、そのディレクトリに含まれるファイルを全部読み込んで、それぞれkey-valueとして保持してくれたりもする。
説明するより例の方が分かり易いので実際のコマンドで説明。
ConfigMapの定義
$ ls redis-config $ cat redis-config maxmemory 2mb maxmemory-policy allkeys-lru
であった時に、このファイルからConfigMapを作成してみると
$ kubectl create configmap <名前> --from-file=redis-config configmap "<名前>" created $ kubectl get configmap <名前> apiVersion: v1 data: redis-config: |- maxmemory 2mb maxmemory-policy allkeys-lru kind: ConfigMap metadata: creationTimestamp: xxxxxxx name: <名前> namespace: default resourceVersion: "6872" selfLink: /api/v1/namespaces/default/configmaps/<名前> uid: 67b1ce21-125c-11e8-9a96-d4c7541dd274
のようになる。dataのところを見ると、ファイル名がキーになって、対応する内容がファイルの中身になっていることが確認できる。
ConfigMapの利用
ConfigMapに保存してある情報をPodで使う時は、ConfigMapの内容をファイルとして書き込んだvolumeを定義し、それをPodがmountして使う。
例
$ cat redis-pod.yaml apiVersion: v1 kind: Pod metadata: name: redis spec: containers: - name: redis image: kubernetes/redis:v1 env: - name: MASTER value: "true" ports: - containerPort: 6379 volumeMounts: - mountPath: /redis-master-data name: data - mountPath: /redis-master name: config volumes: - name: data emptyDir: {} - name: config configMap: name: example-redis-config items: - key: redis-config path: redis.conf
上記のように、volumeを定義するときに、ConfigMapを選び、keyでデータを、pathでボリューム内に作成するファイルを指定する。ここでは、redis.confというファイル(中身はConfigMap内のredis-configで指定したデータ)があるボリュームが作成される。Podではそれを/redis-master
ディレクトリにマウントしていることから、Pod内ではConfigMapの内容は/redis-master/redis.conf
として参照可能となる。
$ kubectl apply -f redis-pod.yaml pod "redis" created $ kubectl exec -it redis /bin/bash root@redis:/data# ls /redis-master redis.conf root@redis:/data# cat /redis-master/redis.conf maxmemory 2mb maxmemory-policy allkeys-lru
[参考]