GCPUG Tokyo June 2018

抽選に初当選したので行ってみた。メモ。

はじめに

GCPUGについて紹介

How to utilize GKE for QA environment / @masudak (mercari)

  • mercariでは、QA環境の中で1年近く前からGKEつかってるらしい。今日はそのお話。
  • 発表者:@masudak
    • SET (Software Engineer in Test)
      • SETは2011年にgoogleが提唱(GoogleはSETからSETI(Software Engineer, Tools & Infrastructure)に移っているというもある模様)
      • mercariは2016/10から導入している --> エンジニアblogに記事あり
  • 開発フロー
    • PMが仕様をJIRAに書く
    • エンジニア・デザイナーがローカルで実装
    • レビュー/
    • QA環境でQAエンジニアがテスト
  • QA環境
    • QAエンジニア/エンジニアがQAする環境
    • 共通のDB、検索データをもつ
    • API/Frontend/Admin/Webなどが動く
      • これらは独立してて、他の人に影響を与えないでテストできる
    • 要件
      • 自分専用/WebUIでデプロイ/スケール/可能な限り本番構成反映
  • 2017春
    • 1年前くらいにmonolithic --> microservice (PHP --> Go)
    • CircleCIの中でデプロイ
      • CircleCIのなかでごにょごにょ
      • PR番号とってきてk8s.yaml(テンプレ)の中の変数をsedで置き換え
      • 外部からのアクセスはリバプロ(Goで実装)でhttp header見て適切なPodにrouting
      • わりと地道なことをやってたみたい(Pod消す時とか)
    • 力技 --> めんてつらい
  • 2017後半
    • Spinnaker
      • CircleCIからはGCRにイメージをpush
      • それをトリガにSpinnakerがデプロイ(pollingしてるらしい)
      • WebUI中心で微妙
      • PR Podsに対応できない = PRごとの環境が作れない
    • CRDを作成(k8sのreconciliation loopを利用)
      • k8s controller for PR
        • ループしてPRの状況を確認する
        • Openだったら既存のRSを複製
        • 環境変数などを書き換え
        • desired stateを書き換え
        • PRのRS完成
        • ひつように応じてexternal-dnsドメイン付与
        • ---> ひょっとしてkustomizeで置き換え可能かも
  • まとめ
    • 開発生産性も品質もまだ追求できる
    • たった1つのVM時代(virtualenvとか使って環境分離??)から2年でここまできた
  • Q&A
    • databaseとかのスキーマ変わるような場合のテストはどうするのか?
      • API gatewayの開発の話なので、DBのスキーマが変わるような変更には対応していない
    • SETの役割
      • テストのやり方とか、こうテストするといいよとか考えているの?
      • CaosEngineeringとか考えているけど、チームとしてはまだ取り組めていない
      • SETは4人

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とは
    • kubeletが直接管理しているPodで、Podがcrashとかするとkubeletが勝手に再起動したりしてくれる
    • apiサーバからは"mirror pod"が見えるけど、apiサーバ経由で操作することは不可能
  • そのため、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がデプロイできました。

[参考]

github.com

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名前解決可能にする条件は以下となる。

  1. headlessサービスであること
  2. Podのhostnameとsubdomainを設定すること

*1: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”

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の補完が有効になるはず。

minikueopenstackコマンドも同じようにして補完を有効にできますね。

*1:kubectl completion -hとかでやり方は確認可能

ConfigMapの使い方

Kubernetesを使う時、アプリの設定とかをコンテナイメージに埋め込むのではなく実行時に設定したい場合、ConfigMapを使う。

ConfigMapはkey-valueで情報を保持しているんだけど、これがなかなか便利で、ファイルから作る*1key: ファイル名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

[参考]

kubernetes.io

*1:kubectl ... --from-file=でファイルを指定

*2:もちろんkeyをファイル名じゃなくて指定したものにすることも可能

*3:kubectl ... --from-file=でディレクトリを指定