nekop's blog

OpenShift / JBoss / WildFly / Infinispanの中の人 http://twitter.com/nekop

Kubernetes/OpenShiftのバージョンアップとクラスタをどのように分けるか問題

Kubernetes/OpenShiftのバージョンアップをどのようにするか、およびクラスタをどのように分けるかという問題はk8s関連のmeetupでよく出る話題です。昨日のレッドハット on Cloud Dayでも出たので、現時点での自分が知っている情報や考えを書いておきます。

現時点で自分が一番しっくりくるそれなりな規模の構成はdev, testing, staging, prodの4種類を2クラスタずつ用意、8クラスタの構成です。規模によってはtesting, stagingは一つにしてしまってもいいと思います。

各ステージのクラスタ、たとえばprod1, prod2はBlue-green deploymentのような形で利用します。たとえばprod1はk8s 1.7, prod2は一つ新しいk8s 1.8となっていて、新規のデプロイは全てバージョンの新しいほうであるprod2に行います。各アプリケーションへのトラフィックはLBでprod1, prod2へ振り分けを切り換えられるように構成します。また、アプリケーションがクラスタを移動する際にストレージのPVのデータの移行がストレートにできないので、その部分は何らかの仕組みを用意する必要があります。

移行期間を置いてprod1のアプリが全部prod2へ移行されたあと、prod1のアップグレードを行ったり、もしくはクリーンに作り直して再度利用可能にし(もちろん事前にprod3を作っておいてprod1を破棄でも良い)、新規のデプロイメントはこちらにデプロイするようにします。このような2クラスタに分ける構成では、片方のクラスタで障害があっても、もう片方のクラスタにすぐデプロイできる体制ができているはずなので、バックアップとしても機能します。また、k8sのアップグレードがアプリケーションに影響しない、もしくは常にクリーンインストールを行うことでアップグレードという作業自体をなくすことができる構成になります。ワークロードはクラスタを分けても分けなくても変わらない、デプロイされているアプリの総数は変わらないはずなので、コンピューティングノードのノード数のオーバヘッドはほどんどありません。

逆にdev, test, prodのワークロードをnode selectorで分けて全部ひとつのクラスタにするという構成もあります。リソース共有の効率化、集約度向上、運用負荷が非常に軽くなるなどの利点も多くありますが、いくつか懸念があります。

  • masterやetcdのバグで環境がダウンするリスク
  • masterやetcdのワークロードはdev, test, prodで共有することになるので、たとえばdevでk8s APIやネットワーク帯域を利用するアプリの負荷テストなどで環境ダウンのリスク
  • バージョンアップで動作しなくなる

k8sはソフトウェアなので当然バグはあります。最近のものを挙げるとmaster APIへのLBの数秒のメンテを行ったらノードが全てNodeNotReadyになってPodも全部NotReadyになるというようなクラスタごと使えなくなるようなバグもやはりあります。そのような状況を許容できる、泣かないのであればクラスタを分けないという選択肢もアリだとは思います。

もちろんクラスタを分けて複数クラスタを運用するコストも小さくはないので、どの程度のAvailabilityやResiliencyが必要かによって構成を選択しましょう。

バージョンアップの問題はさまざまです。master/etcdが起動しなくなるものや、Podのvalidationのルールが変わって以前のバージョンでは動いてたものが動かなくなったり、それらを消そうとしても新しいバージョンのvalidationに引っかかって消えないPodやnamespaceができたり、それら消えない中途半端なオブジェクトが残っているとバージョンアップのプロセスがエラーになって進まなかったり、というような感じです。根本的な解決にはetcdのデータを直接修正したり消したりというような、それなりの知識が必要かつ危険度の高いアクションが必要になることがあります。アップグレードの前に少なくともスナップショット取得、バックアップリストアは必ずテストしましょう。

あとはどのk8sを選択すればいいか、どのような運用体制が必要かという話があったのでついでに。

  • Non-managed k8s
    • 中規模から大規模
    • クラウド上のVMだけどManagedは使えないもしくはオンプレ必須となる特殊要件
    • k8s環境をゴリゴリカスタマイズしたい
    • インフラ5人フルタイム
  • OpenShift
    • どの規模でもオンプレでもクラウドでもいける
    • レールに乗ればとりあえず始められる、という状況が欲しい人。必要なコンポーネントが揃っていてこうやってつかえばいい、というユースケースがある程度用意されている
    • インフラ2人フルタイム
  • Managed k8s (GKE, AKS, EKS, etc)
    • インフラ専任いらない

なぜか小規模なのにオンプレNon-managed k8sを選択してしまって大変、という話をたまに聞きますが、Managed使った方がいいと思います。オンプレk8sは立ち上げのコストが高いので、安易に手を出すと時間が溶けやすいので注意しましょう。

また、Managedを使うにしてもGKE, AKS, EKS, etc.どれを使えばいいのですか、という話もありましたが、既にどれかクラウド環境を使っているような方はManagedなデータベースなど他の機能への連携もありますしとりあえずそのままそちらのサービスを使う、ということが多いのではないかと思います。そのような前提が無いのであれば、どれを利用しても良いんじゃないかと思います。

Container SIG Meet-up 2018 SummerでKubernetesのServiceとかIngressの話をしました

Container SIG Meet-up 2018 SummerでKubernetes Service, Ingress and OpenShift Routerというお題でお話しました。

資料は以下に置いています。

slide.comはRed Hatのコーポレートアカウントを使っているのですが、設定が変わったようでpublicの設定ができないようになっていて、token付きのexternalのURL共有でなんか微妙な感じに。slide.comはプレゼンテーション用に画像でインポートしたものなので、文字やリンクをクリックしたい場合はPDFを利用してください。

さて、最後にService IPでアクセスしてるとコネクションベースのヘルスチェックがないのでノードダウン時に40秒間くらいアクセスできたりできなかったりするので注意してね、という話をしたのですが、その生ログを一応載せておきます。

以下2 replicaのpodのClusterIPへのアクセスです。末尾kwgjfbkz58の2つのPodが確認できます。

[cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo
Wed May 23 03:22:51 EDT 2018
hello-sinatra-3-kwgjf
[cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo
Wed May 23 03:22:51 EDT 2018
hello-sinatra-3-bkz58
[cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo
Wed May 23 03:22:53 EDT 2018
hello-sinatra-3-bkz58
[cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo
Wed May 23 03:22:54 EDT 2018
hello-sinatra-3-kwgjf
[cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo
Wed May 23 03:22:55 EDT 2018
hello-sinatra-3-bkz58
[cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo
Wed May 23 03:22:56 EDT 2018
hello-sinatra-3-kwgjf
[cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo
Wed May 23 03:22:57 EDT 2018
hello-sinatra-3-kwgjf

片方のPodが載っているノードをpoweroff -fしてダウンの状況にします。curlを再開するとハングしたりするのでCtrl-cしたりしないといけなくなったり、curl: (7) Failed connect to 172.30.81.27:8080; No route to hostが返却されたりするのが確認できます。生きているPodへ振り分けられた場合は問題なくレスポンスが返却されています。

$ date; ssh stack86-33 sudo poweroff -f
Wed May 23 03:24:06 EDT 2018
Powering off.
^CKilled by signal 2.
[cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo
Wed May 23 03:24:26 EDT 2018
^C
[cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo
Wed May 23 03:24:29 EDT 2018
hello-sinatra-3-kwgjf
[cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo
Wed May 23 03:24:30 EDT 2018
hello-sinatra-3-kwgjf
[cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo
Wed May 23 03:24:31 EDT 2018
hello-sinatra-3-kwgjf
[cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo
Wed May 23 03:24:32 EDT 2018
hello-sinatra-3-kwgjf
[cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo
Wed May 23 03:24:33 EDT 2018
hello-sinatra-3-kwgjf
[cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo
Wed May 23 03:24:34 EDT 2018
^C  
[cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo
Wed May 23 03:24:38 EDT 2018
hello-sinatra-3-kwgjf
[cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo
Wed May 23 03:24:38 EDT 2018
curl: (7) Failed connect to 172.30.81.27:8080; No route to host

クライアント側で地道にタイムアウトとリトライができる場合はそれでいいのですが、既存のソフトウェアを動かしたいようなユースケースでそのあたりの設定ができない場合とか困ります。今のところこれをうまくハンドリングする定石みたいなものはないと思っているのですが、何か良い案があれば教えてください。OpenShiftのhttp/TLSトラフィックでの限定的なワークアラウンドとしては、Route作ってアクセスするとOpenShift RouterのHAProxyを通るのでHAProxyがヘルスチェックつきで接続管理とロードバランスしてくれます。将来的にはIstioとかがデフォルトでいい感じにハンドリングしてくれることを期待しています。

KubernetesのReadWriteOnceなvolume

よく聞かれるトピックでブログに書いてたと思ってたんだけど実は書いてなかったようだ。Amazon EBSとかGCEPersistentDiskとかAzureDiskとかGluster Block Storageとかを利用するReadWriteOnceのvolumeのお話。

https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes

access modeがReadWriteOnceというvolumeはいわゆるブロックデバイスタイプのvolumeであり、複数のホストから同時に同一のvolumeを利用することはできない(もし実際に複数ホストから同時に書き込まれるとデータが壊れる)。複数のホストから同時に利用できないので、ReplicaSetでスケールさせた場合にpodを複数ホストに分散させることはできない。このためReplicaSetで利用する場合はホスト障害やホストのメンテナンス停止への耐性はなく、ReadWriteOnceを利用するpodは必ず一旦停止してから別ノード上にRecreateされる必要があるためゼロダウンタイムでのpod再デプロイは不可能となる。後述するクラスタ対応ソフトウェアでStatefulSetであればReadWriteOnceを利用しても問題ない。

このようなReadWriteOnceのハンドリングは実際にはvolume pluginで実装されている。podを作成する時の前処理として、Amazon EBSなどのvolume pluginは、volumeをホストにattachする。逆に、podが削除されるときには、volumeをdetachするようになっている。正常な場合の再デプロイでは、コンテナ停止に伴いvolumeをコンテナが動作していたホストからdetach、コンテナ再スケジュール、作成に伴いvolumeをコンテナが動作するホストへattach、という流れで利用される。

https://github.com/kubernetes/kubernetes/tree/master/pkg/volume

volumeがattachされているホストが障害となったときは、volumeがdetach状態にならないまま利用中のステータスで残ることがある。ホスト障害とは言っても、単にネットワークが不通である可能性があるのでホストが実際にダウンしているかどうか判断することはできず、ホストの状態は「Unknown=不定」と考えなければならない。ホスト自体は実際には健康に動作していてvolumeにデータ書き込んでいるかもしれない。このような場合、volumeがdetachされずにpodが別ホストで再スケジュールされるような状況が発生するが、volumeが他ホストにattachされたままになっているのでpod作成時の前処理のvolumeのattachは失敗する。volumeを利用中のホストが本当にダウンしているかどうかもわからないため、強制的にdetachしてattachというわけにもいかない。このようなケースでは大抵マニュアルでの対処が必要となり、現在attachされているホストが停止していることを確認したのち、ブロックストレージの管理インタフェースなどからマニュアルでvolumeをdetachすることで再度そのvolumeが利用可能な状態になり、podの作成処理が進むようになる。volumeがattachされているホストが障害になったときに、自動でホストのダウンを確認してdetachしてくれるようなブロックストレージもあるかもしれないが、基本的にはそのような期待はしないほうがいい。

このように一見ReadWriteOnceはReadWriteManyより制約が多く劣っているように見えるが、劣っているのではなくストレージの性格が異なることを理解する必要がある。ReadWriteOnceのブロックデバイスはfsyncなどを利用するシビアなディスク書き込みを行うようなソフトウェア、いわゆるデータベースやデータストア系のソフトウェアのストレージとして適している。ReadWriteManyをサポートするNFSやGlusterFSのようなネットワークファイルシステムは、そのようなソフトウェアのストレージとして利用すると、一般的に不整合やデータ破損が発生しやすい。また、大抵このようなデータベース系ソフトウェアはReplicaSetでのpodのスケーリング、つまり同一のデータ領域を参照する複数インスタンスの同時起動をサポートしていないため、基本的にReadWriteManyにするユースケースがない。例えばOpenShiftのログ基盤で利用されているElasticSearchは、NFSやGlusterFSだとデータ破損が発生するので、NFSなどのネットワークストレージの利用をサポートしておらず、ReadWriteOnceのブロックデバイスを利用する必要がある。

分散を前提としていてクラスタリングをサポートしているetcd、ElasticSearch、Cassandraなどのソフトウェアでは、ネットワーク経由でのデータ複製や同期機能が備わっているので、PodごとにPVを割り当てることのできるStatefulSetを利用することにより、ReadWriteOnceのストレージを利用していてもスケール可能であり、適切に設定されている限りホスト障害などのときも必要な復旧作業と並行してリクエストに応答できるようになっているはずなのでダウンタイムなしで運用することができる。

OpenShiftにfluentd公式イメージのdaemonsetをデプロイする

OpenShiftにはConfigMapで設定変更できるfluentdが用意されているので、このブログ記事の内容は大抵の人は必要ないはずです。特定の新しいバージョンのfluentdが必須であるとか、あえて別にfluentd公式イメージをデプロイする理由がある人だけ読んでください。

https://github.com/fluent/fluentd-kubernetes-daemonset と対応するリファレンスKubernetes Logging with Fluentdリポジトリ上のfluentd-daemonset-elasticsearch-rbac.yaml見ると、Service account fluentd/var/logをhostPathマウントして読み込むのでrootアクセスが必要、privilegedコンテナにする必要があるようです。privileged sccをfluentdサービスアカウントに追加して、containerのsecurityContext.privilegedをtrueに変更、podを再作成して反映させます。

oc project kube-system
oc create -f https://raw.githubusercontent.com/fluent/fluentd-kubernetes-daemonset/master/fluentd-daemonset-elasticsearch-rbac.yaml
oc adm policy add-scc-to-user privileged -z fluentd
oc patch ds fluentd -p "spec:
  template:
    spec:
      containers:
      - name: fluentd
        securityContext:
          privileged: true"
oc delete pod -l k8s-app=fluentd-logging

このprivilegedコンテナにするだけの変更でとりあえず動いているように見えます。対応するElasticSearchを用意して確認まではしてません。

privilegedコンテナでroot権限の設定なので対象イメージが信頼できる場合のみ設定しましょう。

AzureのAKSのkubectl describe nodeを見る

Kubernetesで実際のメモリを超えるコンテナアプリを動かすと、どうなるか? - あさのひとりごとにスケジューリングリソースの解説がされていますが、一番重要なkubectl describe nodeの生のデータが掲載されていないのがちょっともったいないかなと思ったので一応貼っておきます。

aksの準備から終了まではこんな感じです。

az login
az provider register -n Microsoft.ContainerService
az provider register -n Microsoft.Network 
az provider register -n Microsoft.Compute
az provider register -n Microsoft.Storage

az group list
az group create -n testaks -l eastus
az aks get-versions -l eastus
az aks create -g testaks -n testaks --node-count 2 --kubernetes-version 1.9.6
az aks install-cli
az aks get-credentials -g testaks -n testaks

kubectl get node
kubectl describe node

az aks delete -g testaks -n testaks

kubectl describe nodeの結果は以下です。Allocatableのmemoryが3319Mi、node末尾0のAllocated resourcesのMemory Requestsが290Mi、node末尾1が294Miなので、この例では両ノード3000Mi程度アロケーション可能です。この状態でrequests.memoryが1.5Gi==1536Miのpodを複数スケジュールしようとすると恐らく3つめでFailedSchedulingとなると思います。1500Miであれば4 podスケジューリングできます。最初からデプロイされるkube-systemのpod群の配置は不定なので、場合によっては少し偏ってしまい元記事のように3 podスケジューリングできて4 pod目がfailするという状況にもなるでしょう。

Name:               aks-nodepool1-16184948-0
Roles:              agent
Labels:             agentpool=nodepool1
                    beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/instance-type=Standard_DS1_v2
                    beta.kubernetes.io/os=linux
                    failure-domain.beta.kubernetes.io/region=eastus
                    failure-domain.beta.kubernetes.io/zone=0
                    kubernetes.azure.com/cluster=MC_testaks_testaks_eastus
                    kubernetes.io/hostname=aks-nodepool1-16184948-0
                    kubernetes.io/role=agent
                    storageprofile=managed
                    storagetier=Premium_LRS
Annotations:        node.alpha.kubernetes.io/ttl=0
                    volumes.kubernetes.io/controller-managed-attach-detach=true
CreationTimestamp:  Fri, 13 Apr 2018 05:10:06 +0000
Taints:             <none>
Unschedulable:      false
Conditions:
  Type                 Status  LastHeartbeatTime                 LastTransitionTime                Reason                       Message
  ----                 ------  -----------------                 ------------------                ------                       -------
  NetworkUnavailable   False   Fri, 13 Apr 2018 05:11:13 +0000   Fri, 13 Apr 2018 05:11:13 +0000   RouteCreated                 RouteController created a route
  OutOfDisk            False   Fri, 13 Apr 2018 05:23:02 +0000   Fri, 13 Apr 2018 05:10:06 +0000   KubeletHasSufficientDisk     kubelet has sufficient disk space available
  MemoryPressure       False   Fri, 13 Apr 2018 05:23:02 +0000   Fri, 13 Apr 2018 05:10:06 +0000   KubeletHasSufficientMemory   kubelet has sufficient memory available
  DiskPressure         False   Fri, 13 Apr 2018 05:23:02 +0000   Fri, 13 Apr 2018 05:10:06 +0000   KubeletHasNoDiskPressure     kubelet has no disk pressure
  Ready                True    Fri, 13 Apr 2018 05:23:02 +0000   Fri, 13 Apr 2018 05:11:08 +0000   KubeletReady                 kubelet is posting ready status. AppArmor enabled
Addresses:
  InternalIP:  10.240.0.4
  Hostname:    aks-nodepool1-16184948-0
Capacity:
 alpha.kubernetes.io/nvidia-gpu:  0
 cpu:                             1
 memory:                          3501592Ki
 pods:                            110
Allocatable:
 alpha.kubernetes.io/nvidia-gpu:  0
 cpu:                             1
 memory:                          3399192Ki
 pods:                            110
System Info:
 Machine ID:                 2c3d39f8fac841cb9df23cf4453420a9
 System UUID:                707CF566-AC50-D649-9A23-F6A03C86DE52
 Boot ID:                    ec9a15b2-af2e-4f84-92a5-5525d78c20f8
 Kernel Version:             4.13.0-1011-azure
 OS Image:                   Debian GNU/Linux 9 (stretch)
 Operating System:           linux
 Architecture:               amd64
 Container Runtime Version:  docker://1.13.1
 Kubelet Version:            v1.9.6
 Kube-Proxy Version:         v1.9.6
PodCIDR:                     10.244.0.0/24
ExternalID:                  /subscriptions/31c0faff-6b3e-4b51-86e2-6c9595e05454/resourceGroups/MC_testaks_testaks_eastus/providers/Microsoft.Compute/virtualMachines/aks-nodepool1-16184948-0
ProviderID:                  azure:///subscriptions/31c0faff-6b3e-4b51-86e2-6c9595e05454/resourceGroups/MC_testaks_testaks_eastus/providers/Microsoft.Compute/virtualMachines/aks-nodepool1-16184948-0
Non-terminated Pods:         (6 in total)
  Namespace                  Name                                     CPU Requests  CPU Limits  Memory Requests  Memory Limits
  ---------                  ----                                     ------------  ----------  ---------------  -------------
  kube-system                kube-dns-v20-7c556f89c5-9grpn            110m (11%)    0 (0%)      120Mi (3%)       220Mi (6%)
  kube-system                kube-dns-v20-7c556f89c5-s8x25            110m (11%)    0 (0%)      120Mi (3%)       220Mi (6%)
  kube-system                kube-proxy-4n2xd                         100m (10%)    0 (0%)      0 (0%)           0 (0%)
  kube-system                kube-svc-redirect-qwrtf                  0 (0%)        0 (0%)      0 (0%)           0 (0%)
  kube-system                kubernetes-dashboard-546f987686-khjvt    100m (10%)    100m (10%)  50Mi (1%)        50Mi (1%)
  kube-system                tunnelfront-6f9ff58869-jxcfn             0 (0%)        0 (0%)      0 (0%)           0 (0%)
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  CPU Requests  CPU Limits  Memory Requests  Memory Limits
  ------------  ----------  ---------------  -------------
  420m (42%)    100m (10%)  290Mi (8%)       490Mi (14%)
Events:
  Type    Reason                   Age                From                                  Message
  ----    ------                   ----               ----                                  -------
  Normal  Starting                 15m                kubelet, aks-nodepool1-16184948-0     Starting kubelet.
  Normal  NodeAllocatableEnforced  15m                kubelet, aks-nodepool1-16184948-0     Updated Node Allocatable limit across pods
  Normal  NodeHasNoDiskPressure    14m (x7 over 15m)  kubelet, aks-nodepool1-16184948-0     Node aks-nodepool1-16184948-0 status is now: NodeHasNoDiskPressure
  Normal  NodeHasSufficientDisk    13m (x8 over 15m)  kubelet, aks-nodepool1-16184948-0     Node aks-nodepool1-16184948-0 status is now: NodeHasSufficientDisk
  Normal  NodeHasSufficientMemory  13m (x8 over 15m)  kubelet, aks-nodepool1-16184948-0     Node aks-nodepool1-16184948-0 status is now: NodeHasSufficientMemory
  Normal  Starting                 12m                kube-proxy, aks-nodepool1-16184948-0  Starting kube-proxy.


Name:               aks-nodepool1-16184948-1
Roles:              agent
Labels:             agentpool=nodepool1
                    beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/instance-type=Standard_DS1_v2
                    beta.kubernetes.io/os=linux
                    failure-domain.beta.kubernetes.io/region=eastus
                    failure-domain.beta.kubernetes.io/zone=1
                    kubernetes.azure.com/cluster=MC_testaks_testaks_eastus
                    kubernetes.io/hostname=aks-nodepool1-16184948-1
                    kubernetes.io/role=agent
                    storageprofile=managed
                    storagetier=Premium_LRS
Annotations:        node.alpha.kubernetes.io/ttl=0
                    volumes.kubernetes.io/controller-managed-attach-detach=true
CreationTimestamp:  Fri, 13 Apr 2018 05:10:11 +0000
Taints:             <none>
Unschedulable:      false
Conditions:
  Type                 Status  LastHeartbeatTime                 LastTransitionTime                Reason                       Message
  ----                 ------  -----------------                 ------------------                ------                       -------
  NetworkUnavailable   False   Fri, 13 Apr 2018 05:11:13 +0000   Fri, 13 Apr 2018 05:11:13 +0000   RouteCreated                 RouteController created a route
  OutOfDisk            False   Fri, 13 Apr 2018 05:23:03 +0000   Fri, 13 Apr 2018 05:10:11 +0000   KubeletHasSufficientDisk     kubelet has sufficient disk space available
  MemoryPressure       False   Fri, 13 Apr 2018 05:23:03 +0000   Fri, 13 Apr 2018 05:10:11 +0000   KubeletHasSufficientMemory   kubelet has sufficient memory available
  DiskPressure         False   Fri, 13 Apr 2018 05:23:03 +0000   Fri, 13 Apr 2018 05:10:11 +0000   KubeletHasNoDiskPressure     kubelet has no disk pressure
  Ready                True    Fri, 13 Apr 2018 05:23:03 +0000   Fri, 13 Apr 2018 05:11:11 +0000   KubeletReady                 kubelet is posting ready status. AppArmor enabled
Addresses:
  InternalIP:  10.240.0.5
  Hostname:    aks-nodepool1-16184948-1
Capacity:
 alpha.kubernetes.io/nvidia-gpu:  0
 cpu:                             1
 memory:                          3501592Ki
 pods:                            110
Allocatable:
 alpha.kubernetes.io/nvidia-gpu:  0
 cpu:                             1
 memory:                          3399192Ki
 pods:                            110
System Info:
 Machine ID:                 5b4fb70dfc744759821a92694f9d5993
 System UUID:                45C3EA49-8E12-0146-964D-5EFB5A4E3E8A
 Boot ID:                    e7160774-de86-4a16-9bd3-9f43af1cdd57
 Kernel Version:             4.13.0-1011-azure
 OS Image:                   Debian GNU/Linux 9 (stretch)
 Operating System:           linux
 Architecture:               amd64
 Container Runtime Version:  docker://1.13.1
 Kubelet Version:            v1.9.6
 Kube-Proxy Version:         v1.9.6
PodCIDR:                     10.244.1.0/24
ExternalID:                  /subscriptions/31c0faff-6b3e-4b51-86e2-6c9595e05454/resourceGroups/MC_testaks_testaks_eastus/providers/Microsoft.Compute/virtualMachines/aks-nodepool1-16184948-1
ProviderID:                  azure:///subscriptions/31c0faff-6b3e-4b51-86e2-6c9595e05454/resourceGroups/MC_testaks_testaks_eastus/providers/Microsoft.Compute/virtualMachines/aks-nodepool1-16184948-1
Non-terminated Pods:         (3 in total)
  Namespace                  Name                         CPU Requests  CPU Limits  Memory Requests  Memory Limits
  ---------                  ----                         ------------  ----------  ---------------  -------------
  kube-system                heapster-6599f48877-mhr5s    138m (13%)    138m (13%)  294Mi (8%)       294Mi (8%)
  kube-system                kube-proxy-2lv22             100m (10%)    0 (0%)      0 (0%)           0 (0%)
  kube-system                kube-svc-redirect-q54pd      0 (0%)        0 (0%)      0 (0%)           0 (0%)
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  CPU Requests  CPU Limits  Memory Requests  Memory Limits
  ------------  ----------  ---------------  -------------
  238m (23%)    138m (13%)  294Mi (8%)       294Mi (8%)
Events:
  Type    Reason                   Age                From                                  Message
  ----    ------                   ----               ----                                  -------
  Normal  Starting                 15m                kubelet, aks-nodepool1-16184948-1     Starting kubelet.
  Normal  NodeAllocatableEnforced  15m                kubelet, aks-nodepool1-16184948-1     Updated Node Allocatable limit across pods
  Normal  NodeHasNoDiskPressure    14m (x7 over 15m)  kubelet, aks-nodepool1-16184948-1     Node aks-nodepool1-16184948-1 status is now: NodeHasNoDiskPressure
  Normal  NodeHasSufficientDisk    14m (x8 over 15m)  kubelet, aks-nodepool1-16184948-1     Node aks-nodepool1-16184948-1 status is now: NodeHasSufficientDisk
  Normal  NodeHasSufficientMemory  14m (x8 over 15m)  kubelet, aks-nodepool1-16184948-1     Node aks-nodepool1-16184948-1 status is now: NodeHasSufficientMemory
  Normal  Starting                 12m                kube-proxy, aks-nodepool1-16184948-1  Starting kube-proxy.

Resource requestとlimitの基本はOpenShiftのResource requestとlimitをどうぞ。

OpenShift CNSでgluster-blockを有効化する

OpenShiftではAnsibleのインベントリにglusterfsグループを定義するとCNSをセットアップしてくれる。

[glusterfs]
node[01:03].example.com glusterfs_devices='[ "/dev/sda" ]'

しかしこの記述でセットアップされるのは普通のglusterfsファイルシステムマウントのみで、ブロックデバイスを提供するgluster-blockはprovisionerのみセットアップされるようだ。nodeホストのセットアップは行われないので、別に実行する必要がある。

CNSのドキュメントをみながらセットアップしてみる。

$ ansible nodes -b -a "yum install iscsi-initiator-utils device-mapper-multipath rpcdind -y"
$ cat << EOF > multipath.conf 
device {
                vendor "LIO-ORG"
                user_friendly_names "yes" # names like mpatha
                path_grouping_policy "failover" # one path per group
                path_selector "round-robin 0"
                failback immediate
                path_checker "tur"
                prio "const"
                no_path_retry 120
                rr_weight "uniform"
        }
EOF
$ ansible nodes -b -m copy -a "src=./multipath.conf dest=/etc/multipath.conf"
$ ansible nodes -b -a "mpathconf --enable"
$ ansible nodes -b -a "systemctl restart multipathd rpcbind"
$ ansible nodes -b -a "systemctl enable multipathd rpcbind"
$ oc project glusterfs
$ oc delete pod --all

きちんと設定できていれば、DaemonSetのglusterfs pod内でgluster-blockdが起動する。oc rshしてsystemctl status gluster-blockdを確認すれば良い。

gluster-blockのstorageclassとsecretを定義する。secretはglusterfsで既に設定されているheketi-storage-admin-secretの値をそのまま流用した。

$ oc project glusterfs
$ oc export secret heketi-storage-admin-secret > heketi-storage-admin-secret.yaml
$ cp -a heketi-storage-admin-secret.yaml gluster-block-secret.yaml 
$ vi gluster-block-secret.yaml  # change name and type
$ diff heketi-storage-admin-secret.yaml gluster-block-secret.yaml 
7,8c7,8
<   name: heketi-storage-admin-secret
< type: kubernetes.io/glusterfs
---
>   name: gluster-block-secret
> type: gluster.org/glusterblock 

$ cat << EOF > gluster-block.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gluster-block
parameters:
  resturl: http://heketi-storage-glusterfs.apps.s.nekop.io
  restuser: admin
  restsecretname: gluster-block-secret
  restsecretnamespace: glusterfs
provisioner: gluster.org/glusterblock
reclaimPolicy: Delete
EOF
$ oc create -f gluster-block-secret.yaml
$ oc create -f gluster-block.yaml

PVCを作ってPVできればOK。できない場合はglusterfsプロジェクトのglusterblock-storage-provisioner-dc podのログを確認。

oc run sleep --image=registry.access.redhat.com/rhel7 -- tail -f /dev/null
oc volume dc/sleep --add -t pvc --name=rwo-block --claim-name=rwo-block --mount-path=/rwo-block --claim-size=1Gi --claim-mode=ReadWriteOnce --claim-class=gluster-block
oc get pvc -w

OpenShiftで外部のコンテナレジストリへpushするビルドを作成する

Kubernetes / OpenShift もくもく会 No. 2です。

OpenShift Container Platform 3.9がリリースされたので、会社にある自分のメインクラスタを3.7から3.9にアップグレードしています。並行して3.4, 3.5 3.6, 3.7のテスト環境のプロビジョニングを仕掛けました。でも仕込みさえ終われば基本的にモニタリングしながら待っているだけなので、空き時間に別のネタをこっちに書いておきます。

OpenShiftには統合されたコンテナregistryが付属しており、基本的にビルドしたイメージはこのregistryに格納されるようになっています。このregistryはOpenShiftのRBAC連携の他、イメージメタデータをOpenShiftのカスタムオブジェクトImageStreamとして管理することによって履歴の保持や更新トリガーなどさまざまな付加機能を実現しています。

とはいえ、外部レジストリを使いたい場合もあるので、その場合のアプリケーションの作成のやり方を書いておきます。

oc new-build https://github.com/nekop/hello-sinatra --to=registry.example.com:5000/test-exregistry/hello-sinatra:latest --to-docker
oc new-app --docker-image=registry.example.com:5000/test-exregistry/hello-sinatra:latest --insecure-registry
oc tag registry.example.com:5000/test-exregistry/hello-sinatra:latest hello-sinatra:latest --scheduled=true

このようにnew-build --toオプションを指定することにより、push先が変更されます。new-buildしただけではデプロイされないので、2番目のコマンドでデプロイの設定を作ります。3番目のコマンドはイメージの更新をpollingする設定です。OpenShiftのregistryではイメージの更新は自動検知しますが、外部レジストリの場合でイメージ更新時に自動再デプロイしたい場合はこのpollingを有効化する必要があります。