えんじにあメモ

試してみた技術とか、たまに家電ネタ

おうちk8sのクラスタアップグレードをしたら少しハマった話

Kubernetes 1.18がリリースされたのでおうちk8sに対してもアップグレードを行いました

その際にいくつかハマったので書き起こしておきます

きっかけ

3/26にオンライン配信で開催された Kubernetes Meetup Tokyo #29 Cluster Upgrade 編 にて、
「kubeadmでのクラスタアップグレード:その光と闇」
という発表がありました

speakerdeck.com

おうちk8sもkubeadmで構築していて、この発表を聞いてちょうどいい機会なので自分もアップグレードしてみようと思ったのがきっかけです

発表資料内に

  • トラブルシューティングも充実してきた→裏を返せばそれだけトラブる
  • 壊れることを前提としたほうがいいかもしれない

との記述がありましたが、うまくいくでしょ、というのとちょっと怖いけどお遊び用クラスタだしいっか、という気持ちでやってみることにしました

クラスタアップグレード

アップグレード自体の手順はkubernetes.ioにある こちら の内容を実施するだけです

補足

1.17.2 -> 1.18.0へのアップグレードで、構成はmaster×1、worker×2です

ハマったとこ

1つめ

masterノードで kubeadm upgrade apply v1.18.0 を実行したところ、途中で以下のようなエラーが連続して出るようになりました

[apiclient] Error getting Pods with label selector "k8s-app=upgrade-prepull-kube-controller-manager" [Get https://172.22.0.151:6443/api/v1/namespaces/kube-sys
tem/pods?labelSelector=k8s-app%3Dupgrade-prepull-kube-controller-manager: net/http: request canceled (Client.Timeout exceeded while awaiting headers)]
[apiclient] Error getting Pods with label selector "k8s-app=upgrade-prepull-kube-scheduler" [Get https://172.22.0.151:6443/api/v1/namespaces/kube-system/pods?
labelSelector=k8s-app%3Dupgrade-prepull-kube-scheduler: net/http: request canceled (Client.Timeout exceeded while awaiting headers)]
[apiclient] Error getting Pods with label selector "k8s-app=upgrade-prepull-etcd" [Get https://172.22.0.151:6443/api/v1/namespaces/kube-system/pods?labelSelec
tor=k8s-app%3Dupgrade-prepull-etcd: net/http: request canceled (Client.Timeout exceeded while awaiting headers)]
[apiclient] Error getting Pods with label selector "k8s-app=upgrade-prepull-kube-apiserver" [Get https://172.22.0.151:6443/api/v1/namespaces/kube-system/pods?
labelSelector=k8s-app%3Dupgrade-prepull-kube-apiserver: net/http: request canceled (Client.Timeout exceeded while awaiting headers)]
I0405 19:59:28.079111   18090 request.go:621] Throttling request took 1.290517695s, request: GET:https://172.22.0.151:6443/api/v1/namespaces/kube-system/pods?
labelSelector=k8s-app%3Dupgrade-prepull-etcd

しかもそこで一旦止めたところ、サーバかかなり重たくなりsshするのも数分かかるように 😞

仕方なくリブートして再度実行したら何故かうまくいきました

2つめ

workerノードの1台で

apt-mark unhold kubelet kubectl && apt-get update && apt-get install -y kubelet=1.18.0-00 kubectl=1.18.0-00 && apt-mark hold kubelet kubectl

を実行後にkubeletの再起動を行ったところうまく立ち上がらず 🤔

あまり原因調査をしないまま、最初から設定やり直そうとし、kubeadm resetしました(ちゃんと調べて対応すればよかったかも)

その後のjoinする際に kubeadm token create --print-join-command で発行されたコマンドを実施したら以下のエラーが

error execution phase kubelet-start: cannot get Node "raspberrypi-node02": nodes "raspberrypi-node02" is forbidden: User "system:bootstrap:aagt70" cannot get
resource "nodes" in API group "" at the cluster scope
To see the stack trace of this error execute with --v=5 or higher

調べてみるとkubernetes1.18.0のバグ?で、kubeadm token createで発行されたtokenだとうまく動かないみたいです
代わりに kubeadm init phase bootstrap-token で発行されたtokenでjoinがうまく動きました

github.com

これでなんとか1.17.2 -> 1.18.0へのアップグレードは成功しました(もう1台のworker nodeは問題なくアップグレードできた 🤔)

まとめ

発表にある通り、kubeadmでのクラスタアップグレードの闇を感じたけどいい経験になった

iTerm2で拡大しようとしたときに"About Shell Integration"のポップアップが出る問題への対処法

MacのiTerm2で「Shift + ⌘ + ;」(Shift + "+")で拡大しようとしたときに "About Shell Integration" のポップアップが出て拡大できない問題への対処法 f:id:shosfs:20200327204303p:plain

環境

macOS Mojave バージョン 10.14.6
iTerm2 Build 3.3.9

対応

ショートカットキーを追加する

Preferences... (⌘ + ,) -> Keys -> Key Bindings から「+」で新規ショートカットキーを追加

Keyboards Shortcut Shift ⌘ +
Action Select Menu Item... -> Make Text Bigger

f:id:shosfs:20200327204749p:plain

以上、おわり 🤓

k8sでConfigMap/Secretsの更新時に環境変数へ反映するデプロイフローについて

はじめに

k8sの CofnigMap/Secrets を環境変数に設定し、アプリケーション(Deployment)から利用するパターンはよくあると思います

デプロイ時にDeployment、CofnigMap/Secretsをまとめてデプロイすると思いますが、CofnigMap/Secrets のみの更新時にはDeploymentに自動で反映されません

これはアプリケーションが起動時に CofnigMap/Secrets の値を環境変数に設定するため、起動後に CofnigMap/Secrets が更新されても反映されないためです

アプリケーションにも環境変数の更新を反映をさせたい場合は、Deployment と CofnigMap/Secrets を一緒に更新すればよいわけです

そこで、CofnigMap/Secrets を更新した際に、Deploymentにローリングアップデートをかけるデプロイフローを考えてみます
(CI/CDフローでも使えるよう、デプロイ時にエディタを起動して…などは行わないフローにします)

ローリングアップデートについて

前述したとおり、CofnigMap/Secrets のみの更新ではDeploymentが更新されないため反映が行われません

一般的なデプロイフローでは、各種リソースのyamlファイルを用意してデプロイを行うと思いますが、反映が行われない際のデプロイでは以下のようになっているはずです

$ kubectl apply -f .
configmap/my-configmap configured
deployment.apps/my-deployment unchanged

ComfigMapのみがconfiguredとなっていて、Deploymentのほうはunchangedになってますね

では、特にアプリケーション側に変更が入っていない場合にどのようにローリングアップデートをかければよいのか🤔

qiita.com

上記の記事で、Deploymentのローリングアップデートが起こる条件がすごく分かりやすく説明されています

  • .spec.templateの情報に変更があったとき
    • 変更はどの値でもよいが、アノテーション(.spec.template.metadata.annotations)が一番影響が少ないと思われる

ローリングアップデートを行う

実際にDeploymentのローリングアップデートを行うデプロイフローをいくつかあげてみます

1. デプロイ時に常にローリングアップデート

CofnigMap/Secrets の更新時を問わず、常にローリングアップデートをかける方法です

$ kubectl apply -f .
$ kubectl rollout restart <target_deployment>

applyで更新が入る場合、無駄にローリングアップデートが2度行われるので微妙ですね 🤔

2. アノテーションを動的に変更する

ローリングアップデートが起こる条件に当てはまるよう、.spec.template.metadata.annotationsをデプロイのたびに書き換えるフローです

利用するyamlは動的に書き換えられるよう、deployment.yaml.templateなどとしておきます

deployment.yaml.template(抜粋)

spec:
  template:
    metadata:
      annotations:
        update_time: ${UPDATE_TIME}

デプロイ時の書き換えはenvsubstを使ってみます

$ UPDATE_TIME=$(date +%s) envsubst < deployment.yaml.template > deployment.yaml
$ kubectl apply -f .

↑の例ではデプロイ時にannotations.update_timeに現在時刻を入れてデプロイしてます

spec.template.annotations.update_time の値がデプロイのたびに書き換わるのでローリングアップデートが行われます 🎉

CofnigMap/Secrets の更新時のみでよければ CofnigMap/Secrets のハッシュ値などを設定しても良いかもしれません

3. Kustomizeを使う

3つ目の方法はKustomizeの configMapGenerator / secretGenerator を使うやりかたです

configMapGenerator/secretGenerator で作成される CofnigMap/Secrets には自動的にハッシュ値のsuffixがつき、参照側も動的に書き換えてくれます

kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- ./deployment.yaml

configMapGenerator:
- name: my-env
  literals:
  - TEST_ENV=hogehoge

deployment.yaml(抜粋)

spec:
  template:
    spec:
      containers:
      - envFrom:
        - configMapRef:
            name: my-env

上記のファイルを用意し、kubectl kustomize . を実行すると、

# 抜粋
apiVersion: v1
data:
  TEST_ENV: hogehoge
kind: ConfigMap
metadata:
  name: my-env-c7d2869f5d
---
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
      - envFrom:
        - configMapRef:
            name: my-env-c7d2869f5d

用意したmy-envに -c7d2869f5d というsuffixが自動的に付いています

実際にデプロイするには

$ kubectl apply -k .

を実行すれば、上記のyamlの内容がデプロイされます

また、configMapGeneratorの内容を変更し、

@@ -7,4 +7,4 @@
 configMapGenerator:
 - name: my-env
   literals:
-  - TEST_ENV=hogehoge
+  - TEST_ENV=fugafuga

再度 kubectl kustomize . を実行すると

apiVersion: v1
data:
  TEST_ENV: fugafuga
kind: ConfigMap
metadata:
  name: my-env-9c976df2hh
---
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
      - envFrom:
        - configMapRef:
            name: my-env-9c976df2hh

-c7d2869f5d だったsuffixが -9c976df2hh に変わっています

spec.template.spec.containers が更新されているため、デプロイ時にローリングアップデートが行われます 🎉

✋注意
Kustomizeでのsuffix付きのリソースはそれぞれ別のリソースとしてデプロイされるため、古くなった CofnigMap/Secrets 自体は残ることになります
--prune を利用したデプロイを行うようにしたり、利用しなくなった CofnigMap/Secrets の定期的な削除ができるようにしておきましょう

4. Helmを使う

Helmを使っている場合は、公式でローリングアップデートを行うためのTipsが公開されています

Helm | Chart Development Tips and Tricks

この方式は 2. アノテーションを動的に変更する にも記載した、アノテーションに CofnigMap/Secrets のハッシュ値を利用するパターンです

すごくシンプルな以下の構成を例とします

mychart
├── Chart.yaml
└── templates
    ├── configmap.yaml
    └── deployment.yaml

mychart/templates/configmap.yaml

apiVersion: v1
data:
  TEST_ENV: hogehoge
kind: ConfigMap
metadata:
  name: my-env

mychart/templates/deployment.yaml(抜粋)

spec:
  template:
    metadata:
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
    spec:
      containers:
      - envFrom:
        - configMapRef:
            name: my-env

annotations.checksum/config に configmap.yaml のsha256のハッシュ値を設定しています

この状態でドライランをすると

$ helm install my-chart ./mychart --dry-run
# 抜粋
# Source: mychart/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    metadata:
      annotations:
        checksum/config: 3b57b72332cd09882b34fd55bbedec6ccf3ccdf77f8a70fdc41ab518c8ac01d0

checksum/configハッシュ値が設定されています

デプロイ後に前回同様 TEST_ENV の値をhogehoge -> fugafugaに変更してdiffを見ると

$ helm diff upgrade my-chart ./mychart
# 抜粋
mychart, my-env, ConfigMap (v1) has changed:
  # Source: mychart/templates/configmap.yaml
  apiVersion: v1
  data:
-   TEST_ENV: hogehoge
+   TEST_ENV: fugafuga
...
mychart, mydeploy, Deployment (apps) has changed:
  spec:
    template:
        annotations:
-         checksum/config: 3b57b72332cd09882b34fd55bbedec6ccf3ccdf77f8a70fdc41ab518c8ac01d0
+         checksum/config: f62c80497f97a4b4877958249a1206a3b38bdcb5d5f498df1358ebd3a169c2b7

spec.template.annotations.checksum/config の値が書き換わっているため、デプロイ時にローリングアップデートが行われます 🎉

まとめ

CofnigMap/Secrets を環境変数に設定して利用する際の、Deploymentのローリングアップデートを行うデプロイフローをいくつかあげてみました

個人的には Kustomize や Helm を利用し、必要に応じて動的にローリングアップデートが行われるフローにするのがよいかと思っています ✋

おうちk8sのPersistentVolumeにNFSを使う

おうちk8sで利用するデータの永続化のためにNFSでのPersistentVolumeを作ってみます

自分が作ったおうちk8sはラズパイ3台構成(master1台/node2台)で、今回はmasterサーバをNFSサーバとして利用します

sminamot-dev.hatenablog.com

NFSクライアントの設定

以下nodeサーバ2台にログインしてから行います

$ sudo apt install nfs-common

クライアント側はこれでおわり

NFSサーバの設定

以下masterサーバにログインしてから行います

# NFSサーバのインストール
$ sudo apt install nfs-kernel-server

# NFSで利用するディレクトリの作成
$ mkdir -p /mnt/share/nfs

# NFSサーバ用の設定
$ echo "/mnt/share/nfs  172.22.1.0/24(rw,async,crossmnt,no_root_squash,no_subtree_check)" | sudo tee -a /etc/exports

# NFSサーバの起動
$ sudo systemctl start nfs-server.service
$ sudo systemctl enable nfs-server.service

NFSサーバ用の設定で指定しているIPレンジはおうちk8sで利用しているサーバのレンジです

自分の環境では

  • master -> 172.22.1.161
  • node1 -> 172.22.1.159
  • node2 -> 172.22.1.162

だったため、上記の設定としています

以上でNFSサーバ/クライアントの設定は終了になります

PV/PVCの作成

確認のためにPV/PVCを作成してみます

grafana用に1GiBのPV/PVCを作ります

事前にmasterサーバに対象のディレクトリを作っておきます

# 以下masterサーバで実行
$ sudo mkdir -p /mnt/share/nfs/grafana

PV

nfs-pv.yaml

kind: PersistentVolume
apiVersion: v1
metadata:
  name: grafana
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: grafana
  nfs:
    server: raspberrypi-master.local
    path: /mnt/share/nfs/grafana

この例ではPVが削除されてもデータが残るよう、persistentVolumeReclaimPolicyにはRetainを指定しますが、
ここ を参考に適宜指定してください

PVC

nfs-pvc.yaml

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: grafana
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: grafana

デプロイします 🚀

$ kubectl apply -f nfs-pv.yaml -f nfs-pvc.yaml

確認

$ kubectl get pv,pvc grafana
NAME                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                STORAGECLASS   REASON   AGE
persistentvolume/grafana   1Gi        RWO            Retain           Bound    monitoring/grafana   grafana                 4d19h

NAME                            STATUS   VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/grafana   Bound    grafana   1Gi        RWO            grafana        4d19h

(monitoringネームスペースでデプロイしてます)

STATUSがBoundとなって無事デプロイされているようです 🎉🎉🎉

まとめ

おうちk8s上にNFSサーバの設定、NFSを利用したPV/PVCを作ってみました

prometheusやgrafanaのデータの永続化はひとまずこれで運用していきます💪

参考

おうちk8sにPrometheus/Grafanaを導入してNature Remoの可視化をする

前回おうちk8sKubernetes Dashboardを導入しました

sminamot-dev.hatenablog.com

今回はPrometheus/Grafanaの導入に加え、自宅に設置しているNature Remoのセンサーを利用して部屋の室温や湿度などを可視化してみたいと思います

🆙 2020/06/26 更新 master環境にもnode-exporterをデプロイするようvalues.yamlの差分を更新しました

🆙 2020/03/17 更新
データ永続化のためにNFSのPV/PVCを利用するよう修正しました

事前準備

namespace

PrometheusとGrafanaをデプロイするnamespaceは何でも良いですが、今回はすべてmonitoringというnamespaceを作ってそこにデプロイしていきます

$ kubectl create ns monitoring

kubensなどを使って作成したnamespaceに切り替えておいてください

Helm

PrometheusとGrafanaについてはHelmを使って入れてみます

まずはHelmのインストールから(Macでの動作を想定)

$ brew install helm

# 確認
$ helm version
version.BuildInfo{Version:"v3.1.0", GitCommit:"b29d20baf09943e134c2fa5e1e1cab3bf93315fa", GitTreeState:"clean", GoVersion:"go1.13.8"}

Prometheus/Grafanaのインストールのために公式のチャートリポジトリを追加します

$ helm repo add stable https://kubernetes-charts.storage.googleapis.com/

追加されたことを確認しておきます

$ helm search repo stable
NAME                                    CHART VERSION   APP VERSION             DESCRIPTION
stable/acs-engine-autoscaler            2.2.2           2.1.1                   DEPRECATED Scales worker nodes within agent pools
stable/aerospike                        0.3.2           v4.5.0.5                A Helm chart for Aerospike in Kubernetes
stable/airflow                          6.1.1           1.10.4                  Airflow is a platform to programmatically autho...
stable/ambassador                       5.3.1           0.86.1                  A Helm chart for Datawire Ambassador
# ...

あとは対象のチャート名を指定してhelm installでインストール可能ですが、設定ファイルを修正して利用したいので、デフォルトの設定ファイルを取得します

# prometheus
$ helm show values stable/prometheus > prometheus-values.yaml

# grafana
$ helm show values stable/grafana > grafana-values.yaml

それぞれのyamlをおうちk8s用に修正していきます

Prometheus

Prometheusについては修正箇所がいくつかあります

kube-state-metrics

デフォルトの設定だと、kube-state-metricsのイメージにarmベースのものが用意されていないため、ラズパイ上で動かすことができません

そこで以前書いた、Github Actionsでの複数アーキテクチャ向けのイメージ作成の方法を使ってラズパイ(arm)向けのイメージを作成しました

sminamot-dev.hatenablog.com

上記の方法で作ってもらってもOKですが、現時点で最新版(v1.9.5)イメージを作ってあるのでそれを使ってもらっても構いません
作ったイメージは コチラ

PV/PVC

helmでPrometheusをインストールする際に、prometheus-serverとprometheus-alertmanagerにはPersistentVolume/PersistentVolumeClaimが必要になります

マネージドサービスでは動的に作ってくれる設定などもありますが、もちろんラズパイ上に立てたk8sにはそんな機能はありません

PV/PVCを使わずemptyDirを利用するように変更もできますが、Podが再起動した際にデータが消えてしまうので、事前にPV/PVCを作っておきましょう

PersistentVolumeはNFSを利用しますがmasterをNFSサーバとして利用する記事は以下で書いてるので、NFSサーバ/クライアントの設定を事前に行ってください

sminamot-dev.hatenablog.com

おうちk8sのmasterサーバにログインし、PVに利用するディレクトリを作っておきます

# masterサーバ上で
$ sudo mkdir -p /mnt/share/nfs/prometheus/{alertmanager,server}

# 各プロセスをNonRootで動かすため作成したディレクトリの所有権も変えておく
$ sudo chown $USER /mnt/share/nfs/prometheus

準備ができたのでPV/PVCを作ります(今回はそれぞれ1GiBを利用する設定)

nfs-pv.yaml

kind: PersistentVolume
apiVersion: v1
metadata:
  name: prometheus-alertmanager
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: alertmanager
  nfs:
    server: raspberrypi-master.local
    path: /mnt/share/nfs/prometheus/alertmanager
---
kind: PersistentVolume
apiVersion: v1
metadata:
  name: prometheus-server
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: prometheus-server
  nfs:
    server: raspberrypi-master.local
    path: /mnt/share/nfs/prometheus/server

nfs-pvc.yaml

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: prometheus-alertmanager
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: alertmanager
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: prometheus-server
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: prometheus-server

デプロイします🚀

$ kubectl apply -f nfs-pv.yaml -f nfs-pvc.yaml -n monitoring

values.yaml

今までの変更点をprometheus-values.yamlにも反映させます

変更差分は以下の通り

@@ -180,7 +180,7 @@
     ## alertmanager data Persistent Volume existing claim name
     ## Requires alertmanager.persistentVolume.enabled: true
     ## If defined, PVC must be created manually before volume will be bound
-    existingClaim: ""
+    existingClaim: "prometheus-alertmanager"

     ## alertmanager data Persistent Volume mount root path
     ##
@@ -188,7 +188,7 @@

     ## alertmanager data Persistent Volume size
     ##
-    size: 2Gi
+    size: 1Gi

     ## alertmanager data Persistent Volume Storage Class
     ## If defined, storageClassName: <storageClass>
@@ -276,10 +276,10 @@
   ## Security context to be added to alertmanager pods
   ##
   securityContext:
-    runAsUser: 65534
+    runAsUser: 1001
     runAsNonRoot: true
-    runAsGroup: 65534
-    fsGroup: 65534
+    runAsGroup: 1001
+    fsGroup: 1001

   service:
     annotations: {}
@@ -396,7 +396,7 @@
   ## kube-state-metrics container image
   ##
   image:
-    repository: quay.io/coreos/kube-state-metrics
+    repository: sminamot/kube-state-metrics
     tag: v1.9.5
     pullPolicy: IfNotPresent

@@ -511,7 +511,7 @@
   ##
   image:
     repository: prom/node-exporter
-    tag: v0.18.1
+    tag: v1.0.0-rc.0
     pullPolicy: IfNotPresent

   ## Specify if a Pod Security Policy for node-exporter must be created
@@ -559,7 +559,8 @@
   ## Node tolerations for node-exporter scheduling to nodes with taints
   ## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/
   ##
-  tolerations: []
+  tolerations:
+    - operator: "Exists"
     # - key: "key"
     #   operator: "Equal|Exists"
     #   value: "value"
@@ -836,7 +837,7 @@
     ## Prometheus server data Persistent Volume existing claim name
     ## Requires server.persistentVolume.enabled: true
     ## If defined, PVC must be created manually before volume will be bound
-    existingClaim: ""
+    existingClaim: "prometheus-server"

     ## Prometheus server data Persistent Volume mount root path
     ##
@@ -844,7 +845,7 @@

     ## Prometheus server data Persistent Volume size
     ##
-    size: 8Gi
+    size: 1Gi

     ## Prometheus server data Persistent Volume Storage Class
     ## If defined, storageClassName: <storageClass>
@@ -953,10 +954,10 @@
   ## Security context to be added to server pods
   ##
   securityContext:
-    runAsUser: 65534
+    runAsUser: 1001
     runAsNonRoot: true
-    runAsGroup: 65534
-    fsGroup: 65534
+    runAsGroup: 1001
+    fsGroup: 1001

   service:
     annotations: {}
@@ -972,7 +973,7 @@
     loadBalancerSourceRanges: []
     servicePort: 80
     sessionAffinity: None
-    type: ClusterIP
+    type: NodePort

     ## Enable gRPC port on service to allow auto discovery with thanos-querier
     gRPC:
  • runAsUser/runAsGroup/fsGroup についてはnodeサーバの所有権を変えたユーザのuid/gidを設定します
    • 自分の環境でのアカウントは1001でした
cat /etc/passwd | grep $USER
sminamot:x:1001:1001::/home/sminamot:/bin/bash
  • node-exporterのイメージはラズパイのCPU温度などを取得できるよう、latestでもあるv0.18.1ではなく、v1.0.0-rc.0を使ってます

  • master nodeの環境の情報も取得できるようnode-exporterをmasterにも入れます

    • nodeExporter.tolerationsの設定を変えてdaemonsetがmasterにも配置されるようにしています

Grafana

GrafanaについてもPV/PVCを作成します

Prometheus同様、おうちk8sのmasterサーバにログインし、PVに利用するディレクトリを作っておきます

# masterサーバ上で
$ sudo mkdir -p /mnt/share/nfs/grafana

PV/PVCを作ります nfs-pv.yaml

kind: PersistentVolume
apiVersion: v1
metadata:
  name: grafana
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: grafana
  nfs:
    server: raspberrypi-master.local
    path: /mnt/share/nfs/grafana

nfs-pvc.yaml

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: grafana
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: grafana

デプロイします🚀

$ kubectl apply -f nfs-pv.yaml -f nfs-pvc.yaml -n monitoring

grafana-values.yamlを修正します

@@ -111,7 +111,7 @@
 ## ref: http://kubernetes.io/docs/user-guide/services/
 ##
 service:
-  type: ClusterIP
+  type: NodePort
   port: 80
   targetPort: 3000
     # targetPort: 4181 To be used with a proxy extraContainer
@@ -186,16 +186,16 @@
 ##
 persistence:
   type: pvc
-  enabled: false
+  enabled: true
   # storageClassName: default
   accessModes:
     - ReadWriteOnce
-  size: 10Gi
+  size: 1Gi
   # annotations: {}
   finalizers:
     - kubernetes.io/pvc-protection
   # subPath: ""
-  # existingClaim:
+  existingClaim: "grafana"

 initChownData:
   ## If false, data ownership will not be reset at startup

デプロイ

ここまででPrometheus/Grafanaのデプロイ準備が完了したのでデプロイしちゃいましょう🚀

$ helm install prometheus stable/prometheus -f prometheus-values.yaml --namespace monitoring
$ helm install grafana stable/grafana -f grafana-values.yaml --namespace monitoring
# 更新時は helm upgrade

各種リソースが正常にデプロイされていればOKです

$ kubectl get all -n monitoring

それぞれWebUIでも表示できるか見てみましょう

# Prometheus
$ open http://$(kubectl get nodes --namespace monitoring -o jsonpath="{.items[0].status.addresses[0].address}"):$(kubectl get --namespace monitoring -o jsonpath="{.spec.ports[0].nodePort}" services prometheus-server)

# Grafana
$ open http://$(kubectl get nodes --namespace monitoring -o jsonpath="{.items[0].status.addresses[0].address}"):$(kubectl get --namespace monitoring -o jsonpath="{.spec.ports[0].nodePort}" services grafana)

f:id:shosfs:20200308222908p:plain

f:id:shosfs:20200308222948p:plain

Grafanaにはログインが必要で、usernameはadmin、passwordは以下コマンドで確認可能です

$ kubectl get secret --namespace monitoring grafana -o jsonpath="{.data.admin-password}" | base64 -D

ログインできればOK f:id:shosfs:20200308223123p:plain

🎉🎉🎉🎉🎉

Grafanaのカスタマイズ

せっかくPrometheus/Grafanaのインストールができたので、Grafanaでなにかダッシュボードを作ってみます

ブラウザでGrafanaを開きログインしたら、Create a data sourceを選択します(画像はすでに追加したあとなので、初回はもしかしたらAdd data sourceかも) f:id:shosfs:20200308224312p:plain

"Prometheus" を選択 f:id:shosfs:20200308224642p:plain

Settings -> HTTP -> URLにhttp://prometheus-serverと入力 f:id:shosfs:20200308225358p:plain

Dashboardsの3つとも"Import"する f:id:shosfs:20200308230101p:plain

Settings から「Save & Test」を押し、正常に設定ができればOK

Dashboards -> Manageから取り込んだ3つのDashboardsが取り込まれればOK f:id:shosfs:20200308230922p:plain

試しに「Prometheus 2.0 Stats」など見てみるとそれっぽいものが表示されるはずです 👍 f:id:shosfs:20200308231401p:plain

その他のダッシュボードは Grafana Labs にいろんなダッシュボードが共有されてて、IDを入力するだけでダッシュボードの作成が可能です

ちなみに自分は このダッシュボード を入れてみました f:id:shosfs:20200308233943p:plain

Nature Remoの可視化

やっと本題のNature Remoの可視化です(遅ぇ)

Nature Remo API用のアクセストークン取得

Nature Remo Cloud API のリクエストにはアクセストークンが必要になります

ここのページ からアクセストークンの生成ができるので、値を控えておきます

exporterのk8sへのデプロイ

PrometheusとGrafanaはmonitoringネームスペースにデプロイしてましたが、Nature Remo用のexporterはremoネームスペースを作ってそこにデプロイしたいと思います

$ kubectl create ns remo

Nature Remo用のexporterを作っている方がいたので使わせてもらいます

github.com

↑のリポジトリにはPrometheus/Grafanaのyamlも用意されていますが、先ほど作って不要なため、Nature Remoのexporterのyamlだけ拝借します

使うのはremo-exporter-dep.yamlremo-exporter-svc.yamlですが、apiVersionの書き方が古かったり、NodePortになってたり(クラスタ内でリクエストできれば良いのでClusterIPでよい)するので、修正を加えたものが以下になります

  • deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: remo-exporter
spec:
  replicas: 1
  selector:
    matchLabels:
      app: remo-exporter
  template:
    metadata:
      labels:
        app: remo-exporter
    spec:
      containers:
        - name: remo-exporter
          image: kenfdev/remo-exporter:latest
          ports:
            - containerPort: 9352
              protocol: TCP
          env:
            - name: OAUTH_TOKEN_FILE
              value: '/etc/secrets/api-keys'
          volumeMounts:
            - name: api-keys-volume
              readOnly: true
              mountPath: '/etc/secrets'
      volumes:
        - name: api-keys-volume
          secret:
            secretName: api-keys
  • service.yaml
apiVersion: v1
kind: Service
metadata:
  name: remo-exporter
  labels:
    app: remo-exporter
spec:
  type: ClusterIP
  ports:
    - port: 9352
      protocol: TCP
      targetPort: 9352
  selector:
    app: remo-exporter

先ほど取得したアクセストークンはsecret/api-keysに記載しておきます

前回のKubernetes Dashboard と同様、kustomizeを使ってデプロイしましょう

kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: remo

resources:
  - deployment.yaml
  - service.yaml

secretGenerator:
  - name: api-keys
    files:
      - secret/api-keys

いざデプロイ🚀

$ kubectl apply -k .

デプロイができたらexporterから値が取得できるか確認しておきます

$ kubectl run -it --rm alpine --image alpine --restart=Never
/ # apk update && apk add curl
/ # curl http://remo-exporter.remo:9352/metrics
# HELP go_gc_duration_seconds A summary of the GC invocation durations.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 8.3553e-05
go_gc_duration_seconds{quantile="0.25"} 0.000161035
# ...

正しく取得できてそうです 🎉

Grafanaの設定

exporterもデプロイできたのであとはGrafanaの設定をするだけです

Grafanaの設定はmonitoringネームスペースなので戻しておきましょう

独自のscrapeを追加するためにextraScrapeConfigsの設定を加えます

grafana-values.yamlextraScrapeConfigsに追記してもいいですが、今回は別ファイルで定義して利用してみます

extraScrapeConfigs.yaml

- job_name: 'nature-remo'
  dns_sd_configs:
    - names: ['remo-exporter.remo']
      port: 9352
      type: A
      refresh_interval: 5s

デプロイします🚀

$ helm upgrade prometheus stable/prometheus -f grafana-values.yaml --set-file extraScrapeConfigs=extraScrapeConfigs.yaml  --namespace monitoring

あとはGrafanaのUIからポチポチグラフを追加していくと… f:id:shosfs:20200309232411p:plain

🎉🎉🎉🎉🎉

ダッシュボードのyamlは以下に置いておくので参考にどうぞ ✋

https://github.com/sminamot/raspi-k8s-monitoring/blob/master/grafana/remo/dashboard/grafana.json

まとめ

おうちk8sにPrometheus/Grafanaの導入と、GrafanaへNature Remoを可視化するダッシュボードを追加しました

最近Helmを少し触ってますが、公式のチャート使うと簡単にデプロイできるのでいいですね

あとHelmが2から3にアップデートされてから構成がシンプルになって扱いやすくなりました(まだ2の情報が多いので公式ドキュメント読むのが良さそう、2と3で公式のドキュメントがちゃんと別れてるのが良い)

今回作ったもの

以下に置いておきます

github.com

参考

おうちk8sにKubernetes Dashboardを導入する

以前作ったおうちk8s の可視化をしてみるべく、Kubernates Dashboard導入の手順まとめておきます

Kubernates Dashboard

インストール

手順自体は公式に書かれていますが、おうちk8s上からNodePort経由で見られるよう構築します

github.com

まず公式のyamlを取得します

$ wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-rc5/aio/deploy/recommended.yaml

kubernetes-dashboardのServiceTypeがClusterIPとなっているため、NodePortへの変更とnodePort指定の変更をしておきます
nodePortは今回30843としましたが何でもよい or 固定しなくて良ければ指定しなくてOKです

@@ -37,9 +37,11 @@
   name: kubernetes-dashboard
   namespace: kubernetes-dashboard
 spec:
+  type: NodePort
   ports:
     - port: 443
       targetPort: 8443
+      nodePort: 30843
   selector:
     k8s-app: kubernetes-dashboard

また、この状態でデプロイすると証明書エラーとなり表示することができないため、オレオレ証明書を作成して設定します

$ mkdir -p secret/certs
$ openssl req -nodes -newkey rsa:2048 -keyout secret/certs/dashboard.key -out secret/certs/dashboard.csr -subj "/C=/ST=/L=/O=/OU=/CN=kubernetes-dashboard"
$ openssl x509 -req -sha256 -days 365 -in secret/certs/dashboard.csr -signkey secret/certs/dashboard.key -out secret/certs/dashboard.crt

さらにKubernetes Dashboardのログインに使うためのサービスアカウントと権限設定のためのyamlも作成します
これは 公式のドキュメント に載っているyamlと同じものです
admin-user.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kubernetes-dashboard

あとはデプロイだけですが、Secretの設定をbase64かけてrecommended.yamlに追記するのは面倒なのでkustomizeを利用したいと思います

以下の内容を kustomization.yaml として保存します

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: kubernetes-dashboard

resources:
  - recommended.yaml
  - admin-user.yaml

secretGenerator:
  - name: kubernetes-dashboard-certs
    files:
      - secret/certs/dashboard.crt
      - secret/certs/dashboard.csr
      - secret/certs/dashboard.key
generatorOptions:
  disableNameSuffixHash: true

certs用のsecretはkustomizeによって作られるので、recommended.yamlkubernetes-dashboard-certsの項目は削除しておきましょう

 ---

-apiVersion: v1
-kind: Secret
-metadata:
-  labels:
-    k8s-app: kubernetes-dashboard
-  name: kubernetes-dashboard-certs
-  namespace: kubernetes-dashboard
-type: Opaque
-
----
-
 kind: ConfigMap
 apiVersion: v1
 metadata:

設定ファイルが全部揃ったのでkustomizeを使ってデプロイしましょう

今までで用意したファイルの構成は以下の通り

.
├── admin-user.yaml
├── kustomization.yaml
├── recommended.yaml
└── secret
    └── certs
        ├── dashboard.crt
        ├── dashboard.csr
        └── dashboard.key

kubernetes1.14からkustomizeがkubectlに統合されているので別途インストールは不要です

# デプロイされるリソースの確認
$ kubectl kustomize .

デプロイします
$ kubectl apply -k .

動作確認

Kubernetes Dashboardをブラウザで開いてみましょう

$ open https://$(kubectl get nodes --namespace kubernetes-dashboard -o jsonpath="{.items[0].status.addresses[0].address}"):30843

f:id:shosfs:20200307234855p:plain

ログインにはトークンが必要なため、secretの値で確認します

$ kubectl -n kubernetes-dashboard get secret $(kubectl -n kubernetes-dashboard get secret | grep admin-user | awk '{print $1}') -o jsonpath="{.data.token}" | base64 -D

出力された値をコピーして「トークン」に入力します f:id:shosfs:20200307235158p:plain

サインインができて正しく表示されればOK 🎉 f:id:shosfs:20200308000848p:plain

metrics-server

公式のドキュメント にもありますが metrics-server を入れることでCPUとメモリ使用量をKubernetes Dashboardに追加することができます

自分のおうちk8sはラズパイ上で動いており、公式のyamlはラズパイ用(arm)向けになっていないのでそこを変更してデプロイします

$ git clone https://github.com/kubernetes-sigs/metrics-server.git

変更点は deploy/kubernetes/metrics-server-deployment.yaml の以下部分

@@ -29,7 +29,7 @@ spec:
         emptyDir: {}
       containers:
       - name: metrics-server
-        image: k8s.gcr.io/metrics-server-amd64:v0.3.6
+        image: k8s.gcr.io/metrics-server-arm:v0.3.6
         args:
           - --cert-dir=/tmp
           - --secure-port=4443
@@ -42,9 +42,13 @@ spec:
           runAsNonRoot: true
           runAsUser: 1000
         imagePullPolicy: IfNotPresent
+        command:
+         - /metrics-server
+         - --kubelet-insecure-tls
+         - --kubelet-preferred-address-types=InternalDNS,InternalIP,ExternalDNS,ExternalIP,Hostname
         volumeMounts:
         - name: tmp-dir
           mountPath: /tmp
       nodeSelector:
         beta.kubernetes.io/os: linux
-        kubernetes.io/arch: "amd64"
+        kubernetes.io/arch: "arm"

commandの変更は 参考にした記事 のまま同様の変更を行いましたが、最新版も同じ修正が必要かどうかは未調査 🤔

デプロイします

$ kubectl apply -f metrics-server/deploy/kubernetes

無事CPU/メモリ使用量も確認できるようになりました 🎉 f:id:shosfs:20200308003955p:plain

まとめ

可視化したからどうなったわけではないですが、グラフィカルに見えるとなんか嬉しいです

久しぶりにkustomize使ってみたら結構いろんな機能が増えていた(知らなかっただけ?)のと、
GitHubのサンプルだけだと分かりづらいな、と思ってたら 公式のDoc も用意されてて(これも知らなかった)、設定ファイルが簡単に書けるようになりました

また、PrometheusとGrafanaの導入も合わせて書こうと思ってたけど、Kubernetes Dashboardだけで結構なボリュームになってしまったので次回書きます

今回作ったもの

以下に置いておきます

github.com

参考

Macの画面キャプチャで高品質なGIFを作る

最近Macの画面キャプチャ(動画)を取る機会があったのでまとめる

きっかけ

git-open という、ターミナルからgitのremoteURLをブラウザで開くCLIツールのGo版(元々npmで提供されていたものを使っていたが、)を作った際に、READMEに動作のアニメーションGIFを置きたいと思った

GIPHY Captureという前にも使ったことのあるアプリを使って画面キャプチャのGIFを作ってみたが、 最高画質の設定で作成しても若干荒く微妙だった 🤔

今回作った方法

GIF作成には Gifski というアプリを使いました

Gifski

Gifski

  • Sindre Sorhus
  • ビデオ
  • 無料
apps.apple.com

Gifskiは動画ファイルを読み込みGIFへの変換を行います

そのため事前に画面キャプチャの動画を撮る必要がありますが、MacのMojave以降だと「Command(⌘) + Shift + 5」のショートカットキーで撮ることができます

撮り方は以下に記載されています(範囲選択なども可能)

support.apple.com

TouchBarが付いているMacであれば録画の停止をTouchBar上から行うことができるので、カーソルが画面内に入らなくて良い(数少ないTouchBarの利点)

録画して保存した動画ファイルをGifskiから開く

f:id:shosfs:20200208004814p:plain

サイズやQualityなどを指定して"Convert"を実行するだけでGIF作成完了 👍

出来上がったGIF

実際にGitHubのREADMEにも上げているものがコチラ https://user-images.githubusercontent.com/26164869/73953393-ae0dd500-4943-11ea-91e2-fd7b4278c71b.gif

すごくキレイ ✨

ちなみにGIPHY Captureで撮った同じキャプチャ(厳密には録画ファイルを再生したとこを撮ったもの)が↓コチラ

なんか薄い 😞

あとがき

ということでこれから画面キャプチャGIF作る際は、Mac標準の画面録画+Gifski でいきたいと思います。

またGIFを作るきっかけとなった git-open ですが、
意外と便利で、自分はgit push後の確認やPR出す際に重宝してるのでよければ使ってみてください ←←

github.com