ビビリフクロウの足跡

とあるインフラSEの勉強&備忘ブログ

ElastAlertでEFKスタックで収集したログを監視する

お久しぶりです。生きております…!

最近はコンテナ基盤の監視やログ収集について勉強しておりました。定番のPrometheus+GrafanaやEFKスタックについてですね。前者ではcadvisor、node-exporterを使った一般的なDockerホスト監視やbackbox-exporterを使った外形監視、後者ではfluentdの設定ファイルの記述方法を勉強してコンテナやsystemdサービス、kernelログを収集する方法について学びました。

ところでEFKスタックでログ収集をしたら、ERRORFAILなどの文字列を検出してSlack等に通知したくなりますよね…? Elastic製品ではWatherという、x-pack(https://www.elastic.co/guide/jp/x-pack/current/index.html)内のコンポーネントを使ってこれを実現できるそうですが、x-packは有償なんです… OSSだけで同じことをやろうとしたら、KibanaをやめてGrafanaを使うという手もありますが、ログの検索/一覧性でGrafanaはKibanaには勝てない…

呆然とさまよっていたところで見つけたのがElastAlertというOSSです! ElastAlertを使うとPrometheusのAlertManagerのようにYAMLベースで監視ルールを書いて、Slackを始めとした様々な媒体に通知することができます。またコンテナイメージKibanaのプラグインがbitsensor社によって提供されているため、導入も簡単です。

使い方はbitsensor社のGitHubリポジトリ上の説明が丁寧なので全ては記載しませんが、概ね以下のように進めればOKです。

  1. elastalertのコンフィグファイル(json)を作成する。
  2. kibanaにelastalertプラグインをインストール
  3. EFKスタックを起動
  4. elastalertを起動

監視ルールはKibana上にElastAlertという項目が追加されているので、Creat ruleからエディタを起動し、以下のようなルールを書きます。

es_host: <Elasticsearchのホスト名/IPアドレス>
es_port: 9200

name: Alert on any error

index: logstash-*
timestamp_field: "@timestamp"

type: any

filter:
- query:
    query_string:
      query: "message:/ERROR/i"

alert_subject: "Error!!"

alert_text_type: alert_text_only
alert_text: "occur error on some components!"

alert:
  - slack

slack_webhook_url: "https://hooks.slack.com/services/XXXXX/YYYYY/ZZZZZ"

Test ボタンで試験的にqueryを発行し、アラートを飛ばすことができます。意図したとおりであればSaveをクリックして、アラートを保存して完了です。

kata containers + firecrackerを試してみた

コンテナのセキュリティレベルを上げるためにホストOSとの分離レベルをあげようというモチベーションで開発されたプロダクトがいくつかあります。gVisorやrunV、kata containersなどがそれです。今回はその中のkata containersを検証してみました。kata containersはfirecrackerというAWSがFargateやLambdaの実行環境として利用しているVMMを利用することもできるので、合わせて使ってみたいと思います。

前提

  • Ubuntu 18.04 LTS のCloud image

検証手順

まず、docker ceをインストールします。kata containersのマニュアルに従い、バージョンは最新ではなく、18.06を用います。

sudo apt-get update
sudo apt-get -y install apt-transport-https \
                        ca-certificates \
                        curl \
                        software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository \
     "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
     $(lsb_release -cs) \
     stable"
sudo apt-get install docker-ce=18.06.3~ce~3-0~ubuntu

次にkata containersとfirecrackerのバイナリをDLし、展開します。

wget https://github.com/kata-containers/runtime/releases/download/1.5.0/kata-static-1.5.0-x86_64.tar.xz
sudo tar -xvf kata-static-1.5.0-x86_64.tar.xz -C /

Dockerのデフォルトruntimeの設定をkata containers + firecrackerとします。

sudo su -
cat <<EOF > /etc/docker/daemon.json
{
  "default-runtime": "kata-fc",
  "runtimes": {
    "kata-fc": {
      "path": "/opt/kata/bin/kata-fc"
    }
  },
  "mtu": 1450,
  "storage-driver": "devicemapper"
}
EOF
exit
sudo systemctl daemon-reload
sudo systemctl restart docker

最後に必須カーネルモジュールの読み込み設定をしてインストールは完了です。

sudo su -
cat <<EOF > /etc/modules-load.d/vhost_vsock.conf
vhost_vsock
EOF
exit
sudo modprobe vhost_vsock

ではnginxを起動して、ホストからnginxプロセスが見えるか確かめてみましょう。

sudo docker run -d nginx
sudo ps -ef | grep nginx

grepコマンドの結果のみになっているはずです。ちなみに、containerdで同じことをやると以下のようになりました。

root      8216  8194  0 22:02 ?        00:00:00 nginx: master process nginx -g daemon off;
systemd+  8275  8216  0 22:02 ?        00:00:00 nginx: worker process
tatsuno+  8278  5237  0 22:02 pts/0    00:00:00 grep --color=auto nginx

StatefulSetのノード障害時の挙動と対応

前回、Kubernetesのノード障害時の挙動について記事を書きました。このときはDeploymentを使って挙動を確かめていたですが、前回の記事を見た方から「StatefulSetだと挙動が違ったはず」とのご意見を頂戴しました。

https://twitter.com/tzkb/status/1116597587186814977

そこでStatefulSetだとノード障害時、どんな挙動になるか試してみました。

クラスタの状態は前回の状態からスタートします。

# kubectl get node
NAME                                 STATUS   ROLES    AGE     VERSION
test-cluster-7ajeznlcoium-master-0   Ready    master   2d22h   v1.14.0
test-cluster-7ajeznlcoium-minion-0   Ready    <none>   2d22h   v1.14.0
test-cluster-7ajeznlcoium-minion-1   Ready    <none>   45h     v1.14.0
test-cluster-7ajeznlcoium-minion-2   Ready    <none>   2d16h   v1.14.0

今回はElasticSearchのステートフルセットをデプロイしてみます。KubernetesのGitリポジトリをクローンします。

# cd ~
# git clone https://github.com/kubernetes/kubernetes

Cluster Add-Onからfluentd-elasticsearchディレクトリに入ります。

# cd kubernetes/cluster/addons/fluentd-elasticsearchds

デフォルトではElasticsearchのStatefulSetはvolumeClaimTemplatesを利用するように書かれていないので、以下のようにes-statefulset.yamlを書き換えます。

# Elasticsearch deployment itself
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: elasticsearch-logging
  namespace: kube-system
  labels:
    k8s-app: elasticsearch-logging
    version: v6.6.1
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
spec:
  serviceName: elasticsearch-logging
  replicas: 2
  selector:
    matchLabels:
      k8s-app: elasticsearch-logging
      version: v6.6.1
  template:
    metadata:
      labels:
        k8s-app: elasticsearch-logging
        version: v6.6.1
        kubernetes.io/cluster-service: "true"
    spec:
      serviceAccountName: elasticsearch-logging
      containers:
      - image: gcr.io/fluentd-elasticsearch/elasticsearch:v6.6.1
        name: elasticsearch-logging
        resources:
          # need more cpu upon initialization, therefore burstable class
          limits:
            cpu: 1000m
          requests:
            cpu: 100m
        ports:
        - containerPort: 9200
          name: db
          protocol: TCP
        - containerPort: 9300
          name: transport
          protocol: TCP
        volumeMounts:
        - name: elasticsearch-logging
          mountPath: /data
        env:
        - name: "NAMESPACE"
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
      # Elasticsearch requires vm.max_map_count to be at least 262144.
      # If your OS already sets up this number to a higher value, feel free
      # to remove this init container.
      initContainers:
      - image: alpine:3.6
        command: ["/sbin/sysctl", "-w", "vm.max_map_count=262144"]
        name: elasticsearch-logging-init
        securityContext:
          privileged: true
  volumeClaimTemplates:
  - metadata:
      name: elasticsearch-logging
    spec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 10Gi

ではおもむろにデプロイしてみます。

# kubectl apply -f es-statefulset.yaml
# kubectl get pod -n kube-system -o wide -l k8s-app=elasticsearch-logging
NAME                      READY   STATUS    RESTARTS   AGE     IP             NODE                                 NOMINATED NODE   READINESS GATES
elasticsearch-logging-0   1/1     Running   0          3m34s   10.100.2.221   test-cluster-7ajeznlcoium-minion-2   <none>           <none>
elasticsearch-logging-1   1/1     Running   0          99s     10.100.0.97    test-cluster-7ajeznlcoium-minion-0   <none>           <none>

ここでtest-cluster-7ajeznlcoium-minion-2のkubeletを停止します。

(test-cluster-7ajeznlcoium-minion-2)
# sudo systemctl stop kubelet

すると。。。

# kubectl get pod -n kube-system -o wide -l k8s-app=elasticsearch-logging
NAME                      READY   STATUS        RESTARTS   AGE     IP             NODE                                 NOMINATED NODE   READINESS GATES
elasticsearch-logging-0   1/1     Terminating   0          11m     10.100.2.221   test-cluster-7ajeznlcoium-minion-2   <none>           <none>
elasticsearch-logging-1   1/1     Running       0          9m56s   10.100.0.97    test-cluster-7ajeznlcoium-minion-0   <none>           <none>

StatefulSetの場合は、PodのTerminate処理が完了してから新しいPodが上がるので、kubeletが反応しない以上Terminate処理が完了できず、stuckしてしまうみたいです。

ではどうするか。。。その際の解の一つがTwitter上でご指摘いただいた方から教わったkube-fencingです。

kube-fencingを使うと、NotReadyなノードが生じた場合、その上のk8sオブジェクトを全て削除してノードをフェンシング(強制再起動)してくれます。k8sオブジェクトの削除対象にはNodeオブジェクトも含まれるので、その上に乗ったPodはTerminateどころかetcdから消えます。したがって新しいPodが間違いなく作られるという仕組みで回避しようというのです。

では早速インストールしてみましょう。ソースコードをDLします。

# cd ~
# git clone https://github.com/kvaps/kube-fencing

manifest群が置いてあるディレクトリに移動します。

# cd kube-fencing/examples

私が管理しているノード群はOpenStack上に乗っているので、openstackコマンドを使ってfencingを実現したいと思います。以下のようにfencing-script.yamlを書き換えます。

apiVersion: v1
kind: ConfigMap
metadata:
  name: fencing-scripts
  namespace: fencing
data:
  fence.sh: |
    #!/bin/bash
    NODE="$1"
    source /openstack/openrc-file
    openstack server reboot --hard $1

fencing-scriptに与えるopenrcファイルを用意し、secretオブジェクトを作成します。Secretオブジェクトを作るために、fencingNamespaceも一緒に作ります。

# kubectl create ns fencing
# kubectl create secret generic openrc-file --from-file=openrc-file=openrc-file -n fencing

作ったSecretをマウントするように、またimageもopenstackコマンドがインストールされたものを指定するようにfencing-agents.yamlを書き換えます。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: fencing-agents
  namespace: fencing
spec:
  replicas: 1
  selector:
    matchLabels:
      app: fencing-agents
  template:
    metadata:
      labels:
        app: fencing-agents
    spec:
      nodeSelector:
        node-role.kubernetes.io/master: ""
      tolerations:
      - key: node-role.kubernetes.io/master
        operator: Exists
        effect: NoSchedule
      hostNetwork: true
      containers:
      - name: fencing-agents
        image: bbrfkr0129/kube-fencing-agents
        command: [ "/usr/bin/tail", "-f", "/dev/null" ]
        volumeMounts:
        - name: scripts
          mountPath: /scripts
        - name: openrc-file
          mountPath: /openstack
      volumes:
      - name: scripts
        configMap:
          name: fencing-scripts
          defaultMode: 0744
      - name: openrc-file
        secret:
          secretName: openrc-file
          defaultMode: 0400

manifestを適用します。

# kubectl apply -f .

ノードがNotReady時にfencingしてほしいノードはlabel付けをしておく必要があります。ワーカーノードをラベル付けしておきます。

# kubectl label node test-cluster-7ajeznlcoium-minion-0 fencing=enabled
# kubectl label node test-cluster-7ajeznlcoium-minion-1 fencing=enabled
# kubectl label node test-cluster-7ajeznlcoium-minion-2 fencing=enabled

これでインストール完了です! 早速kubeletを停止させてみましょう。

(test-cluster-7ajeznlcoium-minion-2)
# sudo systemctl stop kubelet

どうなるかというと。。。今度は新しいPodが上がってきません。。。

NAME                      READY   STATUS     RESTARTS   AGE     IP            NODE                                 NOMINATED NODE   READINESS GATES
elasticsearch-logging-0   1/1     Running    0          3m51s   10.100.0.98   test-cluster-7ajeznlcoium-minion-0   <none>           <none>
elasticsearch-logging-1   0/1     Init:0/1   0          53s     <none>        test-cluster-7ajeznlcoium-minion-0   <none>           <none>

describeでログを見ると当然で、PVが違うインスタンスにアタッチされていて、移動できない旨のメッセージが表示されます。しかもたちの悪いことにノードオブジェクトは削除/再作成が行われているため、自分にPVがアタッチされていることを覚えていません。

ではやはりkube-fencingでもStatefulSetは救えないのか。。。というと、実は最後の砦があります。それは

もう諦めて、StatefulSetにはファイルシステムベース(NFSなど)のPVを使う、ということです。こうすれば、PVの取り合いが起きてもどのノードもPVをアタッチ/マウントできるので問題を回避できます。nfs-clientexternal-storage-provisionerなどを使って、興味がある方は試してみてください。

結論としては

  • kube-fencingでもBlock DeviceベースのStatefulSetは救えない。FileSystemベースなら大丈夫。

ということでした。

Kubernetesのノード障害時の挙動とノード復帰後の再スケジューリング

Kubernetes上のアプリケーションの可用性について調べていたところ、ノード障害時に気をつけたほうが良い点を見つけたので記事にしてみます。

Kubernetesのノード障害時にはノードのステータスがNotReadyになります。Kubernetesクラスタが以下の通り構成されていたとしましょう。

# kubectl get node
NAME                                 STATUS   ROLES    AGE    VERSION
test-cluster-7ajeznlcoium-master-0   Ready    master   147m   v1.14.0
test-cluster-7ajeznlcoium-minion-0   Ready    <none>   147m   v1.14.0
test-cluster-7ajeznlcoium-minion-1   Ready    <none>   147m   v1.14.0
test-cluster-7ajeznlcoium-minion-2   Ready    <none>   147m   v1.14.0

さらに、Podは次のように起動していたとしましょう。

NAME                     READY   STATUS    RESTARTS   AGE   IP            NODE                                 NOMINATED NODE   READINESS GATES
httpd-6b77d6648-4jl4z    1/1     Running   0          20s   10.100.0.13   test-cluster-7ajeznlcoium-minion-0   <none>           <none>
httpd-6b77d6648-h7kjb    1/1     Running   0          20s   10.100.1.18   test-cluster-7ajeznlcoium-minion-1   <none>           <none>
httpd-6b77d6648-vd9xs    1/1     Running   0          20s   10.100.2.14   test-cluster-7ajeznlcoium-minion-2   <none>           <none>
nginx-7db9fccd9b-g8fjl   1/1     Running   0          26s   10.100.1.17   test-cluster-7ajeznlcoium-minion-1   <none>           <none>
nginx-7db9fccd9b-lp4fv   1/1     Running   0          26s   10.100.0.12   test-cluster-7ajeznlcoium-minion-0   <none>           <none>
nginx-7db9fccd9b-xdkfn   1/1     Running   0          27s   10.100.2.13   test-cluster-7ajeznlcoium-minion-2   <none>           <none>

ここで、ワーカーノードtest-cluster-7ajeznlcoium-minion-2のkubeletサービスを停止します。

(test-cluster-7ajeznlcoium-minion-2)
# sudo systemctl stop kubelet

40秒ほど経つと、STATUSNotReadyになるはずです。

# kubectl get node
NAME                                 STATUS     ROLES    AGE    VERSION
test-cluster-7ajeznlcoium-master-0   Ready      master   148m   v1.14.0
test-cluster-7ajeznlcoium-minion-0   Ready      <none>   149m   v1.14.0
test-cluster-7ajeznlcoium-minion-1   Ready      <none>   149m   v1.14.0
test-cluster-7ajeznlcoium-minion-2   NotReady   <none>   148m   v1.14.0

ノードがNotReadyだから、Podも移動しているだろう。。。と思いませんか? ですが。。。

NAME                     READY   STATUS    RESTARTS   AGE     IP            NODE                                 NOMINATED NODE   READINESS GATES
httpd-6b77d6648-4jl4z    1/1     Running   0          3m50s   10.100.0.13   test-cluster-7ajeznlcoium-minion-0   <none>           <none>
httpd-6b77d6648-h7kjb    1/1     Running   0          3m50s   10.100.1.18   test-cluster-7ajeznlcoium-minion-1   <none>           <none>
httpd-6b77d6648-vd9xs    1/1     Running   0          3m50s   10.100.2.14   test-cluster-7ajeznlcoium-minion-2   <none>           <none>
nginx-7db9fccd9b-g8fjl   1/1     Running   0          3m56s   10.100.1.17   test-cluster-7ajeznlcoium-minion-1   <none>           <none>
nginx-7db9fccd9b-lp4fv   1/1     Running   0          3m56s   10.100.0.12   test-cluster-7ajeznlcoium-minion-0   <none>           <none>
nginx-7db9fccd9b-xdkfn   1/1     Running   0          3m57s   10.100.2.13   test-cluster-7ajeznlcoium-minion-2   <none>           <none>

2分経っても移動しません。。。

実は、v1.13.0からTaintBasedEvictionというPod退避機能がデフォルトで有効になったようです。この機能ではTaintとeffectNoExecuteなTolerationを使って、Podを退避させます。具体的にはノードがNotReadyになると自動的にノードにtaint node.kubernetes.io/not-readyが付くようになり、加えて全てのPodに対してデフォルトのtolerationとしてこのtaintに対するNoExecute tolerationが付与されます。これにより当該機能を実現しています。

デフォルトではPod退避の猶予期間であるtolerationSecondsパラメータは300秒(5分)です。このため、デフォルトでは5分経たないとPodは再スケジューリングされなかったのです。この猶予期間を短くするには、kube-apiserverの起動オプションに以下を指定します。この例では30秒にしています。

  --default-not-ready-toleration-seconds=30
  --default-unreachable-toleration-seconds=30

では再チャレンジ。。。

NAME                     READY   STATUS        RESTARTS   AGE    IP            NODE                                 NOMINATED NODE   READINESS GATES
httpd-6b77d6648-7hf2x    1/1     Running       0          4m7s   10.100.0.14   test-cluster-7ajeznlcoium-minion-0   <none>           <none>
httpd-6b77d6648-dshdc    1/1     Running       0          4m8s   10.100.1.21   test-cluster-7ajeznlcoium-minion-1   <none>           <none>
httpd-6b77d6648-l9tkd    1/1     Terminating   0          4m7s   10.100.2.15   test-cluster-7ajeznlcoium-minion-2   <none>           <none>
httpd-6b77d6648-qjmfm    1/1     Running       0          12s    10.100.1.24   test-cluster-7ajeznlcoium-minion-1   <none>           <none>
nginx-7db9fccd9b-hh5gk   1/1     Terminating   0          4m1s   10.100.2.16   test-cluster-7ajeznlcoium-minion-2   <none>           <none>
nginx-7db9fccd9b-q9psw   1/1     Running       0          12s    10.100.1.23   test-cluster-7ajeznlcoium-minion-1   <none>           <none>
nginx-7db9fccd9b-r2td2   1/1     Running       0          4m1s   10.100.0.15   test-cluster-7ajeznlcoium-minion-0   <none>           <none>
nginx-7db9fccd9b-wq2hg   1/1     Running       0          4m1s   10.100.1.22   test-cluster-7ajeznlcoium-minion-1   <none>           <none>

見事、30秒程度で退避できました! でもちょっとまってください。もしこの状態でノードが復活したらどうなるでしょう。

NAME                                 STATUS   ROLES    AGE    VERSION
test-cluster-7ajeznlcoium-master-0   Ready    master   173m   v1.14.0
test-cluster-7ajeznlcoium-minion-0   Ready    <none>   174m   v1.14.0
test-cluster-7ajeznlcoium-minion-1   Ready    <none>   174m   v1.14.0
test-cluster-7ajeznlcoium-minion-2   Ready    <none>   173m   v1.14.0

Podの稼動状態はこのようになりました。

NAME                     READY   STATUS    RESTARTS   AGE     IP            NODE                                 NOMINATED NODE   READINESS GATES
httpd-6b77d6648-7hf2x    1/1     Running   0          5m51s   10.100.0.14   test-cluster-7ajeznlcoium-minion-0   <none>           <none>
httpd-6b77d6648-dshdc    1/1     Running   0          5m52s   10.100.1.21   test-cluster-7ajeznlcoium-minion-1   <none>           <none>
httpd-6b77d6648-qjmfm    1/1     Running   0          116s    10.100.1.24   test-cluster-7ajeznlcoium-minion-1   <none>           <none>
nginx-7db9fccd9b-q9psw   1/1     Running   0          116s    10.100.1.23   test-cluster-7ajeznlcoium-minion-1   <none>           <none>
nginx-7db9fccd9b-r2td2   1/1     Running   0          5m45s   10.100.0.15   test-cluster-7ajeznlcoium-minion-0   <none>           <none>
nginx-7db9fccd9b-wq2hg   1/1     Running   0          5m45s   10.100.1.22   test-cluster-7ajeznlcoium-minion-1   <none>           <none>

test-cluster-7ajeznlcoium-minion-2には再スケジューリングされません。Podが片寄状態になってしまっています。もしこの状態でたくさんPodがのったノードが停止してしまうと。。。

という事態を防ぐためにdeschedulerというプロダクトがあります!

これはKubernetesのJobとして機能させることで、Podのスケジューリングの偏りを検知して、再スケジューリングをかけてくれます。早速導入してみます。

git clone https://github.com/kubernetes-incubator/descheduler
cd descheduler/kubernetes
kubectl apply -f rbac.yaml
kubectl apply -f configmap.yaml
cat <<EOF | kubectl apply -f -
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  labels:
    run: descheduler-cronjob
  name: descheduler-cronjob
  namespace: kube-system
spec:
  concurrencyPolicy: Allow
  jobTemplate:
    spec:
      template:
        metadata:
          name: descheduler-pod
          annotations:
            scheduler.alpha.kubernetes.io/critical-pod: ""
        spec:
            containers:
            - name: descheduler
              image: bbrfkr0129/descheduler:latest
              volumeMounts:
              - mountPath: /policy-dir
                name: policy-volume
              command:
                - "/bin/descheduler"
              args:
                - "--policy-config-file"
                - "/policy-dir/policy.yaml"
                - "--v"
                - "3"
            restartPolicy: "Never"
            serviceAccountName: descheduler-sa
            volumes:
            - name: policy-volume
              configMap:
                name: descheduler-policy-configmap

  schedule: '*/3 * * * *'
EOF

この例ではCronJobを用いて3分ごとに再スケジューリングするようにしています。

3分後、見事偏りがなくなりました。。。!!

NAME                     READY   STATUS    RESTARTS   AGE   IP            NODE                                 NOMINATED NODE   READINESS GATES
httpd-6b77d6648-7hf2x    1/1     Running   0          14m   10.100.0.14   test-cluster-7ajeznlcoium-minion-0   <none>           <none>
httpd-6b77d6648-dshdc    1/1     Running   0          14m   10.100.1.21   test-cluster-7ajeznlcoium-minion-1   <none>           <none>
httpd-6b77d6648-zvznn    1/1     Running   0          44s   10.100.2.18   test-cluster-7ajeznlcoium-minion-2   <none>           <none>
nginx-7db9fccd9b-hqj72   1/1     Running   0          44s   10.100.2.20   test-cluster-7ajeznlcoium-minion-2   <none>           <none>
nginx-7db9fccd9b-q9psw   1/1     Running   0          10m   10.100.1.23   test-cluster-7ajeznlcoium-minion-1   <none>           <none>
nginx-7db9fccd9b-r2td2   1/1     Running   0          14m   10.100.0.15   test-cluster-7ajeznlcoium-minion-0   <none>           <none>

Rancher Submarinerを動かしてみた

3/12にRancher公式ブログで発表された、「Submariner」という新しいOSSを動かしてみましたので、軽く所感を書きます。

Submarinerってなに?

複数のKubernetesクラスタのPodネットワークおよびServiceネットワークを繋げるプロダクトです。NodePortサービスを作って外部公開せずとも、複数Kubernetesクラスタ内のPod同士で通信することが可能になります。

公式では以下の用途にマッチするとしています。

利用手順概要

ここからは私が試した手順の概要を記載いたします。といっても公式GitHubが秀逸なので、特に面白い内容ではないですが。。。

submarinerを試すには最低限3つのKubernetesクラスタが必要です。2つは実際に接続したいクラスタ「west」と「east」、残りの1つは「broker」という、westとeastのネットワーク情報を同期する仲介役として動きます。 クラスタ要件として以下が求められるので、ユーザが自由に制御可能なバニラKubernetesで試すのがおすすめです。

  * 任意のクラスタ間でpodネットワークcidrおよびserviceネットワークcidrおよびcluster local domainが異なること
  * 任意のクラスタ間でインターネットを通じて直接通信できるか、同じネットワークに所属していること
  • brokerクラスタに対し、以下のコマンドを実行
# helmの初期化
kubectl -n kube-system create serviceaccount tiller
kubectl create clusterrolebinding tiller \
  --clusterrole=cluster-admin \
  --serviceaccount=kube-system:tiller
helm init --service-account tiller
helm repo update

# submariner-brokerのインストール
SUBMARINER_BROKER_NS=submariner-k8s-broker
helm install submariner-latest/submariner-k8s-broker \
--name ${SUBMARINER_BROKER_NS} \
--namespace ${SUBMARINER_BROKER_NS}
SUBMARINER_BROKER_URL=$(kubectl -n default get endpoints kubernetes -o jsonpath="{.subsets[0].addresses[0].ip}:{.subsets[0].ports[0].port}")
SUBMARINER_BROKER_CA=$(kubectl -n ${SUBMARINER_BROKER_NS} get secrets -o jsonpath="{.items[?(@.metadata.annotations['kubernetes\.io/service-account\.name']=='${SUBMARINER_BROKER_NS}-client')].data['ca\.crt']}")
SUBMARINER_BROKER_TOKEN=$(kubectl -n ${SUBMARINER_BROKER_NS} get secrets -o jsonpath="{.items[?(@.metadata.annotations['kubernetes\.io/service-account\.name']=='${SUBMARINER_BROKER_NS}-client')].data.token}"|base64 --decode)
  • IPSec通信するための事前共有キーの作成
SUBMARINER_PSK=$(cat /dev/urandom | LC_CTYPE=C tr -dc 'a-zA-Z0-9' | fold -w 64 | head -n 1)
  • westクラスタに対し、以下のコマンドを実行
# eastクラスタに対する通信のゲートウェイとなるノードにラベル付け
kubectl label node <GATEWAY_NODE> "submariner.io/gateway=true"

# helmの初期化
kubectl -n kube-system create serviceaccount tiller
kubectl create clusterrolebinding tiller \
  --clusterrole=cluster-admin \
  --serviceaccount=kube-system:tiller
helm init --service-account tiller

# submarinerのインストール
helm install submariner-latest/submariner \
--name submariner \
--namespace submariner \
--set ipsec.psk="${SUBMARINER_PSK}" \
--set broker.server="${SUBMARINER_BROKER_URL}" \
--set broker.token="${SUBMARINER_BROKER_TOKEN}" \
--set broker.namespace="${SUBMARINER_BROKER_NS}" \
--set broker.ca="${SUBMARINER_BROKER_CA}" \
--set submariner.clusterId="west-cluster" \
--set submariner.clusterCidr="<westクラスタのpod network cidr>" \
--set submariner.serviceCidr="<westクラスタのservice network cidr>" \
--set submariner.natEnabled="false"
  • eastクラスタに対し、以下のコマンドを実行
# westクラスタに対する通信のゲートウェイとなるノードにラベル付け
kubectl label node <GATEWAY_NODE> "submariner.io/gateway=true"

# helmの初期化
kubectl -n kube-system create serviceaccount tiller
kubectl create clusterrolebinding tiller \
  --clusterrole=cluster-admin \
  --serviceaccount=kube-system:tiller
helm init --service-account tiller

# submarinerのインストール
helm install submariner-latest/submariner \
--name submariner \
--namespace submariner \
--set ipsec.psk="${SUBMARINER_PSK}" \
--set broker.server="${SUBMARINER_BROKER_URL}" \
--set broker.token="${SUBMARINER_BROKER_TOKEN}" \
--set broker.namespace="${SUBMARINER_BROKER_NS}" \
--set broker.ca="${SUBMARINER_BROKER_CA}" \
--set submariner.clusterId="east-cluster" \
--set submariner.clusterCidr="<eastクラスタのpod network cidr>" \
--set submariner.serviceCidr="<eastクラスタのservice network cidr>" \
--set submariner.natEnabled="false"
  • westクラスタ上にnginx deploymentとserviceを作る(動作確認)
kubectl run nginx --image nginx
kubectl expose deploy nginx --port 80
  • 作成したサービスのIPを取得(動作確認)
NGINX_SERVICE_IP=$(kubectl get svc nginx -o jsonpath="{.spec.clusterIP}")
kubectl run busybox --restart=Never --image busybox:1.28 -- sleep 3600 
kubectl exec -it busybox -- wget -q -O - ${NGINX_SERVICE_IP}

無事、nginxのスタートページが表示されればOKです。

まとめ

submarinerをざっと試してみましたが、そもそもKubernetesクラスタはプロジェクトやチームなど意味あるかたまりで分割されるであろうことから、そもそもpodネットワークやserviceネットワークをつなぎたい需要ってそんなにあるのかな? と疑問に思っています。 Rancherの提示している使い方以外に使えるとすれば、PVのクラスタ間移行なんかには便利に使える気はしますね。

現時点ではクラスタ超えのDNSには対応していないので、クラウドネイティブに使うとなるとちょっと物足りないところ。今後の発展に期待です!

Kubernetesの認定資格「CKA」を取得しました 〜これから受ける方へのアドバイス〜

この度、Kubernetesの認定資格「CKA(Certified Kubernetes Administrator)」を取得いたしました! 今後受ける方のお役に立てばと、受験レポートを残しておきます。

要約 〜忙しい人はここだけ読んでね〜

  • 試験に関する説明の書かれている英語のドキュメント「Handbook」「Important Tips」を熟読しよう!
  • Kubernetes完全ガイド」の4章から8章は可能な限り、9章から12章は余力と相談して押さえておこう!
  • Kubernetes The Hard Way」を1度実施し、各オペレーションの意味を理解しておこう!

受験動機

私の場合は、今まで培ってきたKubernetesの技術知識を試すことが目的でした。また副次的な目的で、社内でコンテナ技術推進活動をしているため、その発言に説得力をもたせる意味もありました。

Kubernetesの技術を身につけるきっかけにする」というパターンの方もいるかと思います。

自分がやったこと

自分が受験をするぞ!と決めてやったことは以下の4つです。私の場合は既にKubernetesを結構触ってしまっていたので役に立つかわからないですが、かけた時間を括弧付けで示しておきます。

  • パスポート取得(1週間)
  • 貸し会議室押さえ(30分)
  • 試験のHandbookとImportant Tipsに目を通す(6時間くらい)
  • Kubernetesの機能復習(8時間くらい)
パスポート取得

CKAを受験するには氏名がローマ字記載の写真付き身分証明書が必要になります。おそらくパスポート一択な気がします。

私は国際旅行等で海外に出たことが一回もなく、人生で初めてパスポートを取得しました。まさか資格試験のためにパスポートを取得することになろうとは…

パスポートの発行は私の居住する地域だと1週間程度かかりました。もし受験する熱意があるけどお持ちでない方は、受験予定日を決めたら早めに取得することをおすすめします。

ちなみに発行料は5年パスポートで1万円程度でした。

貸し会議室押さえ

CKAの受験は準備も含めて3時間半程度、誰もいない、誰も入ってこない、静かで、机・椅子以外ほぼ何もないスペースが必要になります。

私の場合は会社の会議室で試験を実施するのが難しい事情があったため、貸し会議室を予約し、受験することにしました。

試験日のスケジューリングをしてから空いている会議室を探すのが良いと思います。2000円くらいあれば借りられると思います。

試験のHandbookとImportant Tipsに目を通す

CKAは国際試験なので現状、試験に関する情報は英語になります。ですので、試験のレギュレーションを理解する時間をしっかり取りました。

Kubernetesの機能復習

先人の方々が残してくれた受験記はチラ見していましたが、なかなか実際の問題のイメージが膨らまなかったので、今まで触ってきた機能の復習は少々行いました。

具体的にはCyberAgentの青山さんが書いた本「Kubernetes完全ガイド」の4章〜12章くらいを見直したことと、Kubernetesクラスタをスクラッチで構築するハンズオン「 Kubernetes The Hard Way」を通しで1回、やり直してみました。

試験当日の対応

試験日当日の流れは以下のようになります。

  • Webカメラ有効化・デスクトップ共有
  • パスポート提示
  • 受験部屋の状態確認
  • Webカメラ位置調整
  • 受験開始
Webカメラ有効化・デスクトップ共有

試験当日は予約した会議室で試験サイトにアクセスし、試験官と英語のチャットでやり取りする形になります。まずはじめにWebカメラを有効化し、デスクトップを有効化するよう、指示されました。

パスポート提示

Webカメラが有効になったら、カメラ経由でパスポートの顔写真が写っているページを試験官に見せます。ここはすんなり通りました。

受験部屋の状態確認

次に試験を受験する部屋を一周くまなくカメラで見せるように指示があります。この際、特に机上を注意してみているようで、机上をゆっくり見せてほしいと指示がありました。

Webカメラ位置調整

試験中は基本的に休憩を申し出ない限りは、受験用端末のスクリーンを見続けていなければなりません。

顔がきれいに試験官に見えていなかったのか、Webカメラを少し下げるように指示されました。ディスプレイに付属のカメラを使う人は辛いディスプレイの角度で受験することになるかもしれないです。私は許容範囲でしたが…

許容できなければ外部Webカメラがあるとよいかも…?!

受験開始

以上の準備を終えて、試験官が試験ページを更新し、問題文とコンソールが表示されました。そしたら試験開始です。開始の合図は特にありませんでした。

試験に受かるために必要だと思うこと

私が考えるCKA合格に必要なことは以下の5点になります。

試験レギュレーションの事前理解

これが大前提になります。試験のルールは全て英語で記載されたHandbook、Important Tipsしかないので、抵抗がある方もいらっしゃるかと思いますが、禁止事項を理解しておかないと即時試験終了されてしまう恐れがあるので、HandbookとImportant Tipsの中身はよく理解するようにすると良いです。

特に、試験中に参照できるWebページの制限(k8sの公式ドキュメント、ブログ、公式GitHubのみ)には注意が必要です。Kubernetes公式ドキュメントからドキュメント検索をかけると、外部ページも引っかかるため、不意にそれらのページリンクをクリックしてしまうと不正行為になります。また、表示できるタブの数(試験サイト以外、一つのみ)も注意が必要なルールの一つです。

kubectlの便利機能の知識

kubectlを使ってオブジェクトの名前だけをリストしたり、特定のラベルを持つオブジェクトだけを検索したりする等、普段使っていない方もkubectlの効率的な使い方は理解しておいたほうが良いでしょう。青山さんの本「Kubernetes完全ガイド」だと4章を網羅的に理解しておけば十分でしょう。

基本的なKubernetesオブジェクトの知識

Deployment、ReplicaSet、Pod、各種Serviceなどの本当に基本的なオブジェクトの作り方から、マニフェストの書き方はしっかり押さえておくべきでしょう。マニフェストのサンプルはKubernetes公式ドキュメントから引っ張ってこれますし、kubectl run --dry-run -o yamlでも確認できるので、ゼロから作るというよりかは各パラメータの意味を理解してカスタマイズできるかが重要になります。

なおその他のオブジェクトについても、私が受けた感覚では「Kubernetes完全ガイド」の5章〜8章は可能な限り押さえ、余力があれば9章、10章を理解しておくぐらいで十分かなと思います。

Kubernetesクラスタの運用の知識

Kubernetesを運用するための知識として、「Kubernetes完全ガイド」の11章の知識は押さえておきましょう。またnode selectortainttolerationなどを用いたPodスケジューリングは覚えておいて損はないと思います。

ツールに頼らないKubernetesクラスタの構築知識

「kubeadm」などのツールを用いてしかKubernetesクラスタを構築したことがない方はぜひ一度Kubernetes The Hard Way」を1度実施してください。そしてオペレーションの一つ一つの意味を理解しておくほうが良いです。

結果 〜スコアと証書〜

上記準備で、無事CKAを取得することができましたが、スコアは80%と、合格ライン74%と比較してギリギリでした。一問以外はわからない問題ではなかったのですが、失点の理由が知りたいのがホンネ…

f:id:bbrfkr:20190306114113p:plain

本レポートが今後CKAを受験する方のお役に立つことを願っております!

KubernetesのHorizontalPodAutoscalerでハマった話

ふとしたきっかけで、deploymentを以下のように定義していたわけです。

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: v1
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: v1
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: v1
    spec:
      containers:
      - image: nginx
        name: nginx
        resources:
          requests:
            cpu: "100m"
            memory: 128Mi
          limits:
            cpu: "100m"
            memory: 256Mi

このマニフェストによるdeployment、kubectl autoscaleでもhpaが機能しません! 以下のように、appラベルはapp: <deployment名>である必要があるみたいです。。。

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        resources:
          requests:
            cpu: "100m"
            memory: 128Mi
          limits:
            cpu: "100m"
            memory: 256Mi

今日、1/3日ぐらいハマってしましました。。。