ビビリフクロウの足跡

いつもお世話になっているインターネットへの恩返し

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日ぐらいハマってしましました。。。

OpenStack Swiftの既存リングを拡張する

OpenStack Swiftで新しくストレージノードを構築し、既存リングを拡張する機会がありましたので、その際の手順をメモしておきます。

手順

proxyノードで以下のコマンドを叩きます。

# accountリングの拡張
swift-ring-builder account.builder add r<region num>z<zone num>-<storage node ip>:6202/<device name> 100
swift-ring-builder account.builder rebalance

# containerリングの拡張
swift-ring-builder container.builder add r<region num>z<zone num>-<storage node ip>:6201/<device name> 100
swift-ring-builder container.builder rebalance

# objectリングの拡張
swift-ring-builder object.builder add r<region num>z<zone num>-<storage node ip>/<device name> 100
swift-ring-builder object.builder rebalance

region番号とzone番号は既存リング情報から得ることができます。以下コマンドからどうぞ。

swift-ring-builder account.builder
swift-ring-builder container.builder
swift-ring-builder object.builder

Portusでプライベートなコンテナイメージレジストリを立ててみる

Dockerを使う際にまずお世話になるのがパブリックなコンテナイメージレジストリサービスであるDocker Hubかと思うのですが、業務で本格的にDockerを使うとなった際にはクローズドなネットワークセグメントでしかDockerホストを実行できなかったりして、プライベートなイメージレジストリが欲しくなりますよね?

そんなときのためにDocker側でプライベートレジストリサーバ用のコンテナイメージが用意されているのですが、UIがないのでイメージを一覧や削除などの管理をしようとすると、直にAPIを叩かなくてはならず辛みがあります。そこでUI付きのプライベートレジストリを探すと、プライベートGitリポジトリでも有名なGitLabが引っかかるのですが、あれってリポジトリだけでなく、CIやKubernetes連携など、いろいろな機能がついているじゃないですか。そこまで仰々しくしたくないんですよね…

シンプルにレジストリとUIと認証周りの機能がついていればいいのよ! というモチベーションで再度探すと、ありました! PortusというOSSが…!

が、例のごとく構築に少しつまずいたので、構築・設定方法を残しておきたいと思います。今後Portusを構築する人のために、つまづきポイントを共有できればと思います。

Portusとは

SUSEが開発している、プライベートレジストリに認証機能とWeb UIを提供するOSSです。 ざっくりとどんなものか知りたい方はCyberAgentの青山さんが記事にしてくれているので、そちらをご参照。

構築手順

では、本題に入っていきましょう。

前提

まず前提を示していきますが、以下のとおりです。

  • OS: CentOS 7
  • IP: 192.168.0.100
  • 作業ユーザ: root
  • 構築方法: docker-composeを利用

PortusもDockerコンテナで動作するので、パッケージの導入手順くらいを置き換えていただければ、Dockerが動作するどんなLinuxでも動作するかと思います。

Dockerのインストール

MinimalのCentOS 7のマシンにログインしたら、まずDockerをインストールします。

yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum install -y docker-ce docker-ce-cli containerd.io

インストールしたらサービスを設定しましょう。

systemctl enable docker
systemctl start docker

docker-composeのインストール

docker-composeを使ってPortusを構築するので、インストールしておきます。

curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/bin/docker-compose
chmod +x /usr/bin/docker-compose

Gitのインストール

Portusのソースコードの中にサンプルのdocker-compose.ymlが入っています。これをDLするためにGitをインストールします。

yum install -y git

Portusのソースコードを取得

Portusのソースコードを取得します。ソースコード/etc/docker/に置くことにします。

cd /etc/docker
git clone https://github.com/SUSE/Portus.git

今回はVer 2.4をデプロイしたいと思います。

cd Portus
git checkout remotes/origin/v2.4
git checkout -b v2.4

以降の作業は/etc/docker/Portus/example/composeで行いますので、移動しておきます。

cd /etc/docker/Portus/examples/compose

証明書群の作成

PortusのWeb UIおよびプライベートレジストリに使われるSSL証明書を作成します。今回は自己署名認証局(CA)を同ホスト内に作り、証明書を発行します。

まずはCAを作成します。

cd secrets
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -subj "/CN=192.168.0.100" -days 7305 -out ca.crt

次にサーバの秘密鍵を作ります。

openssl genrsa -out portus.key 2048

CSRを発行して、CAで署名付き証明書を作ります。

cat <<EOF > csr.conf
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn

[ dn ]
C = JP
ST = Tokyo
L = Shinjuku
#O = <organization>
#OU = <organization unit>
CN = 192.168.0.100

[ req_ext ]
subjectAltName = @alt_names

[ alt_names ]
IP.1 = 192.168.0.100

[ v3_ext ]
authorityKeyIdentifier=keyid,issuer:always
basicConstraints=CA:FALSE
keyUsage=keyEncipherment,dataEncipherment
extendedKeyUsage=serverAuth,clientAuth
subjectAltName=@alt_names
EOF

openssl req -new -key portus.key -out portus.csr -config csr.conf
openssl x509 -req -in portus.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out portus.crt -days 10000 -extensions v3_ext -extfile csr.conf
cd ..

.envファイルの修正

docker-composeに読み込ませる.envファイルを修正します。

cat <<EOF > .env
MACHINE_FQDN=192.168.0.100

SECRET_KEY_BASE=$(openssl rand -hex 64)
PORTUS_PASSWORD=password
DATABASE_PASSWORD=p@ssw0rd
EOF

nginx.confの修正

nginxの設定ファイルnginx.conf内のserver_nameパラメータを修正します。

sed -i 's/server_name.*/server_name 192.168.0.100;/g' nginx/nginx.conf

initスクリプトの修正

今回作成したCAをプライベートレジストリに信頼させるため、以下の記述をregistry/initに追加します。

#!/bin/sh

set -x

cp /secrets/portus.crt /usr/local/share/ca-certificates
# here!!
cp /secrets/ca.crt /usr/local/share/ca-certificates

update-ca-certificates
registry serve /etc/docker/registry/config.yml

Portusの起動

あとはdocker-composeで起動するだけです。

docker-compose up -d

以上で、構築は完了です。あとはPortus上で初期設定をしていきましょう。

設定手順

Portus上で設定を行うためにhttps://192.168.0.100にアクセスしましょう!

管理ユーザ設定

まずは管理ユーザであるadminユーザを作ります。

f:id:bbrfkr:20190202203426p:plain

レジストリサーバ登録

レジストリサーバ登録をして…

f:id:bbrfkr:20190202203457p:plain

ユーザ作成

一般ユーザbbrfkrを作ります。

f:id:bbrfkr:20190202203511p:plain f:id:bbrfkr:20190202203540p:plain

再ログイン

そして再ログイン。

f:id:bbrfkr:20190202203553p:plain f:id:bbrfkr:20190202203605p:plain f:id:bbrfkr:20190202203636p:plain

実際にイメージをプッシュしてみる

作ったレジストリサーバにログインするためにはCA証明書をクライアントマシンの適切な場所に置く必要があります。以下の場所に置きましょう。

/etc/docker/certs.d/192.168.0.100/ca.crt

その後レジストリサーバにログインして、nginx:latestをプライベートレジストリにプッシュしてみます。

docker login 192.168.0.100
docker pull nginx:latest
docker tag nginx:latest 192.168.0.100/bbrfkr/nginx:v1.0
docker push 192.168.0.100/bbrfkr/nginx:v1.0

プッシュが成功すると、Portus上で確認できるようになります。

f:id:bbrfkr:20190202203652p:plain

KubeVirtを使ってみる

KubeVirtをお試ししてみたので、その際のメモを残しておきます。

KubeVirtとは

KubeVirtはRed Hat社が主に開発している、CNCF MemberプロダクトのOSSです。KubeVirtを使うとKubernetes上で仮想マシンを管理することができるようになります。これによりコンテナよりも隔離レベルの高い仮想マシンを使いながら、KubernetesのCloud Nativeな機能であるPodネットワーク、Service、サービスディスカバリを仮想マシンに対し適用することができるようになります。

KubeVirtのインストール

インストールは至極簡単です。Kubernetesクラスタを用意したら、以下のコマンドを実行するだけです。

export VERSION=v0.11.0
kubectl create -f https://github.com/kubevirt/kubevirt/releases/download/$VERSION/kubevirt.yaml

もし、Workerノードが仮想化支援機構(IntelでいうVT-x)に対応しておらず、ハードウェアエミュレーションを使いたい場合は、事前に以下のコマンドを実行します。

kubectl create configmap -n kube-system kubevirt-config --from-literal debug.useEmulation=true

KubeVirtを使ってみる

KubeVirtで仮想マシンを操作するには、専用のCLIツールであるvirtctlがあると便利です。virtctlをインストールしましょう。

curl -L -o virtctl https://github.com/kubevirt/kubevirt/releases/download/$VERSION/virtctl-$VERSION-linux-amd64
chmod +x virtctl
sudo chown root:root virtctl
sudo mv virtctl /usr/bin

インストールしたらまずは超軽量OSであるCirrOSを起動してみましょう。仮想マシンオブジェクトをKubernetes上に作成します。

kubectl apply -f https://raw.githubusercontent.com/kubevirt/demo/master/manifests/vm.yaml

kubectl get vmコマンドを実行すると、以下のような出力が得られると思います。

NAME     AGE   RUNNING   VOLUME
testvm   6s    false

RUNNIGfalseとあるように、この時点ではまだ仮想マシンは起動していません。起動にはvirtctlコマンドを使って以下のようにします。

virtctl start testvm

すると、RUNNINGtrueに変わります。

NAME     AGE   RUNNING   VOLUME
testvm   1m    true

実は仮想マシンオブジェクトはコンテナで言うReplicaSetのようなもので、仮想マシンの起動定義情報が格納されているだけです。実際の仮想マシンのポインタ情報は「仮想マシンインスタンスオブジェクト」に格納されています。仮想マシンインスタンスオブジェクトを一覧するにはkubectl get vmiコマンドを実行します。

NAME     AGE   PHASE     IP           NODENAME
testvm   3m    Running   10.100.0.6   kubevirt-test-jbsl2irizcpz-minion-1

すると、上記のように仮想マシンの現在の状態やIP情報などを手に入れることができます。

仮想マシンのコンソール画面(CUIベース or GUIベース)にアクセスするには、またvirtctlコマンドを使います。

virtctl console testvm
virtctl vnc testvm

しかし、Cloudイメージを使う場合、通常パスワード認証できないので、これらのコマンドを使う機会は少ないでしょう。

仮想マシンSSHログインするにはコンテナと同様に仮想マシンを Service で外部公開する必要があります。これもvirtctlコマンドで簡単に行なえます。

virtctl expose vm testvm --name testvm --port 22 --type NodePort

kubectl get svc -o wideすると、確かにNodePort Serviceが作成されてますね!

NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE   SELECTOR
kubernetes   ClusterIP   10.254.0.1    <none>        443/TCP        18m   <none>
testvm       NodePort    10.254.20.5   <none>        22:30296/TCP   64s   kubevirt.io/domain=testvm,kubevirt.io/size=small

どこかのNodeの30296番ポートをsshで突っついてみましょう。ユーザ名はcirros、パスワードはgocubsgoです。

ssh -p 30296 cirros@<NodeのIP>
$

無事ログインできましたか?

仮想マシンの停止にもvirtctlを使います。

virtctl stop testvm 

仮想マシンの削除にはkubectl delete vmコマンドです。これを実行すると該当の仮想マシンインスタンスも一緒に消えます。

kubectl delete vm testvm

最後に

いかがでしたでしょうか。仮想マシンの隔離レベルを担保しなくてはいけないけれど、コンテナは使えないといったケースや、コンテナネットワークに仮想マシンを直接繋いでレイテンシを改善したい場合などに使えそうかなと思っています。 時間を見つけて任意の仮想マシンイメージを起動する方法も書こうと思いますので、お楽しみに!