この本、
15Stepで習得 Dockerから入るKubernetes コンテナ開発からK8s本番運用まで (StepUp!選書)
- 作者:高良 真穂
- 発売日: 2019/09/27
- メディア:単行本(ソフトカバー)
このエントリは、その15 Stepの各ステップで、前回のエントリで作ったEC2環境でどう扱うかに絞ったメモです。
あくまで、この本が手元にある状況での補足説明です。手元にないと、さっぱりわからないと思います。
Step 01 コンテナ最初の一歩
ここはKubernetesクラスター自体は不要で、Dockerのノード1つとレジストリがあればOKです。
なので、シングルで動かすならMasterとして立てたインスタンス1つだけ、あえて環境分離をするならば、master+Worker1つで実施します。
Docker Hubなど外部リポジトリを使うときは、プライベートリポジトリ自体不要ですね。
Step 02 コンテナの操作
ここもDocker1つに閉じた話なので、MasterノードのみでOKです。
Ubuntuのカスタムイメージ作成時に、実行例4で改行後に含まれているdnstoolsとcurlを入れ忘れるとStep04で躓きます。
Step 03 コンテナ開発
ここもDockerの世界で、Dockerfileの書き方とビルドの実行が中心ですので、MasterノードのみでOKです。
サンプルはgithubに公開されているので、Masterノードにはgitもインストールしておいてgit cloneしておきましょう。
Step 04 コンテナとネットワーク
Dockerで起動しているコンテナにネットワークアクセスする話なので、複数台構成で実施しましょう。
Step 05 コンテナAPI
Step 06 Kubernetes最初の一歩
ここからやっとKubernetesの演習。
基本的にはmasterノードで操作すればOKです。
なお、git cloneしてくる一式の中に、step06はありません。
06.3 コントローラによるポッドの実行
バックグラウンド実行をする際に
kubectl run hello-world --image=hello-world
が記載されていて、注意書きで
kubectl create deployment hello-world --image=hello-world
を使え、と書いてありますが、この2つで起動するものの数が違ってしまうようです。
後者(create deployment)を使うと、書籍の結果と同じ4つがget allで確認できるようになりました。
実行例12、13で「pod」を「po」と書いているのが、次のページまで行かないと省略系「pod->po」「deployment->deploy」が使える説明が出てこないですね。初見びっくりなヤツです。
実行例14で「kubectl get deployment」の表示項目に「DESIRED」がありますが、実行例15の「kubectl get deploy,po」のdeploymentの表示項目には「DESIRED」がなかったり、難しいですね。
Step 07 マニフェストとポッド
07.4 ポッドのヘルスチェック機能
実行例8でdocker buildを実行する際、docker hubではなくプライベートレジストリにする場合、
docker build --tag リポジトリ:タグ
での名称を
プライベートレジストリ名:5000/webapl:0.1
に変更し、docker pushも
docker push プライベートレジストリ名ノード:5000/webapl:0.1
で実行します。
つまり、この環境ではmasterノードがプライベートリポジトリを兼ねているので
「k8s-master:5000/webapl:0.1」
のように書きます。
マニフェストファイル内で指定しているイメージがmaho/xxxになっている場合もプライベートリポジトリに向けること。(この先扱うマニフェストファイルはすべて同じ対応が必要)
※レジストリを立てたEC2を起動し、registryコンテナをdocker runで起動しておくこと。masterノードをインスタンスごと停止、再起動した場合、docker stert registryしておくこと。
※ここで動かすサンプルはもともと一定時間でヘルスチェックエラーになって、Pod内のコンテナが再起動を繰り返す(kubectl get po でRESTART値が少しずつ増えていく)仕様になっている。
Step 08 デプロイメント
08.2 スケール機能
実行例4を実施する際、
「kubectl get deploy,po」で表示されるdeploymentの「NAME」は、
書籍だと「deployment.extensions/web-deploy」だが、
こちらの環境だと「deployment.app/web-deploy」となりました。
08.6 自己回復機能
実行例11で、いわゆるフェイルオーバーの様子を観察しますが、workerノード1つをノードまるごとシャットダウンして5分程度経過したとき、ダウンしたworkerで動いていた数のpodが活きているworkerで復活してREPLICASの数まで復旧します。
このときの旧podのステータスは、
- 書籍「Unknown」
- 環境「Terminating」
というように表示がちょっと違いました。
ダウンノードが再起動した後、Unknown(Terminating)だったpodが表示から消えます。
なお、リバランシングは発生せず、偏ったままになります。
08.7 デプロイメントを利用した高可用性構成
学習環境2で実施するにしても、EC2で実施するにしても、実行例13の「kubectl apply -f mysql-act-stby.yml」を実行する前に、StorageClassの作成をしておく必要があります。
このことは、著者提供の学習環境2構築用のvagrant設定一式
https://github.com/takara9/vagrant-kubernetes
の中のReadmeに書いてあります。
上記をgit cloneしたあと、
cd vagrant-glusterfs/k8s-yaml kubectl apply -f storageclass.yml
を実行して、StorageClassを作成してから、実行例13を実行します。
なお、ここで、PersistentVolume、PersistentVolumeClaimがうまく作成できない場合は、heketiとの通信、GlusterFSとの通信がうまくいってなかったり、GlusterFS側の容量が足りていなかったりします。
heketi-cliから容量確認して、うまく開放できていなかったらVolumeを削除するなどします。
k8sのノード側にGlusterFSクライアントを入れ忘れているときも同様です。
私はそれでもうまく行かず、環境作り直しました。
StorageClass、PersistentVolume、PersistentVolumeClaimまではうまく作成できるのに、GlusterFSをマウントするPodがContainerCreatingで止まってしまう場合は、GlusterFSのクライアントとサーバーでバージョンがズレていたり、という可能性があります。
前述の「storageclass.yml」の「parameters」に
volumeoptions: "ctime off"
を追加してから、「kubectl apply -f storageclass.yml」を実行します。
6.0 - Gluster Docs
を読む限り、バージョン差異を吸収するような目的のパラメータではないのですが、
mout glusterfs volume suddenly failing · Issue #658 · gluster/glusterfs · GitHub
に、この設定で回避できる旨が記述されていたので、これに従うことで解消しました。
08.8デプロイメントの自己修復
実際にmysqlが動いているnodeを落として、AVAILAVLEが0になるのに数十秒、フェイルオーバーが始まるのは、そのAVALABLE=0になってから5分くらいしてからです。
Step 09 サービス
09.8 セッションアフィニティ
直前の「09.7サービスの作成と機能確認」の最後にオブジェクトをまるごと消す方法(「 kubectl delete -f . 」の案内がありますが、消してしまうと実行例7が動かないです。
Step 10 ジョブとクーロンジョブ
10.2 ジョブの同時実行数と実行回数
実行回数を指定して、その回数終わったら終了するjobの例ですが、
githubから持ってきたサンプルのjob-normal-end.ymlのcompletionsは「5」、書籍の方は「6」と書いてありますね。
実行例1、実行例2にある「kubectl get jobs」の表示も微妙に違います。これはkubernetesのバージョンの差かな。
10.3 ジョブが異常終了するケース
job-normal-end.ymlに続き、job-abnormal-end.ymlも、中身が違います。
backoffLimitが、github側は「10」、書籍側は「3」
command:のSleepの時間は、github側は「10」、書籍側は「5」
でした。
10.4 コンテナの異常終了とジョブ
job-container-failed.ymlに至っては、github側にはファイル自体が存在しませんでした。
10.5 素数計算ジョブの作成と実行
この時期(2019秋)執筆でPython2というのがちょっと気になったり、インストールするpip が古いとか、色々警告は出るますが、一応問題なく動きます。
プライベートリポジトリにイメージをPushするので、イメージのタグ修正だけ気をつけましょう。
10.6 メッセージブローカーとの組み合わせ」「10.7 KubernetesAPIライブラリの利用
ソース解説はあるが、実行しない!まさかの節跨ぎ!
10.8 Kubenetes APIジョブの投入と実行
実行例12で、pn_generator-queの下にあるDockerFileをbuildします。
タグはプライベートリポジトリに向けて作成し、docker pushもしておきます。
実行例14のdocker buildですが、
py/job-initiator.pyの中身の編集が必要です
「# メッセージ・ブローカーと接続」
のところで、
#qmgr_host='172.16.20.11' # for vagrant-k8s qmgr_host='192.168.99.100' # for minikube
となっているところを、for vagrant-k8s側を有効化、IPアドレスはマスターノードのIPアドレスに修正します。(マスターに限らず、クララスターのいずれかのノードならいいらしい)
minikube側の行はコメントアウトします。
また、同じファイルの
「# ジョブのマニフェスト作成」
のところで、コンテナイメージを指定しているところがありますので、ここを自分のプライベートリポジトリにあるコンテナを指すように修正します。
なぜか、実行例でpn_generator:0.2としてbuild とpushをしているのに、ここは「0.7」になっているので、どちらかに揃えましょう。
そして、これでもまだbuildに失敗します。
Complete output from command python setup.py egg_info: pkg_resources/py2_warn.py:15: UserWarning: Setuptools no longer works on Python 2 ************************************************************ Encountered a version of Setuptools that no longer supports this version of Python. Please head to https://bit.ly/setuptools-py2-sunset for support. ************************************************************ warnings.warn(pre + "*" * 60 + msg + "*" * 60) ---------------------------------------- Command "python setup.py egg_info" failed with error code 32 in /tmp/pip-build-orUdKp/setuptools/ You are using pip version 8.1.1, however version 20.1.1 is available. You should consider upgrading via the 'pip install --upgrade pip' command. The command '/bin/sh -c pip install pika kubernetes' returned a non-zero code: 1
pipが古すぎるみたいです。
DockerFileで、
「RUN pip install pika kubernetes」
の直前に
「RUN pip install --upgrade pip」
を追加してから、buildしましょう。これで一応docker buildは通ります。
実行例15では、job-initはk8sではなくDokcerで起動します。
job-initをビルドしたディレクトリから実行します。
なお、実行例15はMinikubeでの実行時のコマンドなので、Minikubeではないときは
docker run -it --rm --name kube -v `pwd`/py:/py -v ~/.kube:/root/.kube プライベートリポジトリ/job-init:0.1 bash
のように、
「-v ~/.minikube:/Users/ユーザー名/」
は不要です。
そして実行例16、実行例17と進むのですが、実行結果、これ違和感あります。
このjobは、素数計算を4並列で実行しているのですが、その4並列の意図は1~1000、1001~2000、2001~3000、3001~4000で分割、と思われます。
しかし、job-initiator.pyが素数計算プログラムをを呼び出すときに使う引数配列が
[[1,1000],[1001,2000],[2001,2000],[3001,4000]]
なのに対して、処理側「prime_numpy.py」が、配列の2つ目を「終端値」じゃなくて「幅」として処理しているので、
1~1000、1001~3000、2000~4000、3001~7000
という範囲で処理してしまっていますね。
たぶん
[[1,1000],[1001,1000],[2001,1000],[3001,1000]]
が意図した動きなんじゃないかと。
Step 11 ストレージ
11.4 永続ボリュームの実際
Minikubeで組んだ環境が大前提の記述です。
StorageClass Standardは環境によって異なるデフォルトのようで、Minikubeだと「HostPath」が該当する、図3と読み取れました。
で、本家のドキュメントを見ると
HostPath (テスト用の単一ノードのみ。ローカルストレージはどのような方法でもサポートされておらず、またマルチノードクラスターでは動作しません)
永続ボリューム - Kubernetes
とあるので、今回作っているマルチノードクラスターでは動きません。
ということで、ここは飛ばします。
実際使わなそうだし。
11.5 既存NFSサーバーを利用する場合
masterノードは/exportをNFS公開するようにNFSサーバーも兼ねて構築しているので、そのように読み替えていきます。
masterノードでkubectlを叩いて、podがworker1,worker2で起動して、そのpodからmasterにNFSマウントしてくる、という構造です。
11.7 SDSによるダイナミックプロビジョニング
SDS=Software Defined StorageとしてGlusterFSを使う演習です。
といっても、Step08でもGlusterFSはでてきているので、同じように進めます。
実行例19でStorageClassを作成する前に、gfs-sc.ymlの中身を自前環境用に修正します。
「resturl」に記述するIPアドレスは、heketiサーバーとして立てたノードのローカルIPアドレスに。
「restuserkey」には、adminユーザーのパスワードを。
さらに、前にも触れましたが、GlusterFSのサーバー、クライアントのバージョンが微妙に揃っていないとつながらない問題を回避するため、
「volumeoptions: "ctime off"」を最後に追加します。
Step 12 ステートフルセット
12.2 マニフェストの書き方
実行例1で「kubectl apply -f mysql-sts.yml」
の前に、「gfs-sc.yml」「mysql-sts.yml」をそれぞれ修正します。
「mysql-sts.yml」については、学習環境に合わせた修正です。
「#storageClassName: gluster-heketi # 容量 12Gi GlusterFS」は、先頭の「#」を外してコメント解除。
「storageClassName: standard # 容量 2Gi Minikube/GKE」は、先頭に「#」をつけてコメントアウト。
「storage: 2Gi」は「12Gi」に修正です。
「mysql-sts.yml」については、11.6と同様の修正を行います。繰り返しになりますが。
「resturl」に記述するIPアドレスは、heketiサーバーとして立てたノードのローカルIPアドレスに。
「restuserkey」には、adminユーザーのパスワードを。
さらに、前にも触れましたが、GlusterFSのサーバー、クライアントのバージョンが微妙に揃っていないとつながらない問題を回避するため、
「volumeoptions: "ctime off"」を最後に追加します。
実行例1のコマンドはminikubeベースの手順です。
先に
kubectl apply -f gfs-sc.yml
してから
kubectl apply -f mysql-sts.yml
を実行します。
次の12.3の演習に向けて、podからexitするものの、pod自体は起動したままにします。
12.3 手動テイクオーバーの方法
Step8でもテイクオーバー(フェイルオーバー)は一度やってますので、特に躓くことはないはずです。
12.4 ノード障害時の動作
ノードごと落とす操作は、Vagrantベースなので「vagrant halt (MySQLのPodが動いている)ノード」を案内されていますが、EC2で動かしていますので、そのノードをシャットダウンします。
マネジメントコンソールからいきなり落としても、ログインしてshutdownコマンドを打って良いです。
kubectl delete node を実行するわけだが、nodeを戻す方法の案内がないです。。。
復旧するには、
master上で
kubeadm token create --print-join-command
した結果の行をまるごとコピーし、シャットダウンしたノードを起動してログインし、root権限で実行します。
しかし
[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`
というメッセージと共にエラーになるので、このメッセージに従い、先程のkubeadmのコマンドに
「--ignore-preflight-errors all」
をつけると、再参加できます。
このノードのロールラベルも取れてしまっているので、初期構築時と同様、
kubectl label node k8s-worker1 node-role.kubernetes.io/worker=
も再実行します。
12.5 テイクオーバー自動化コードの開発
動かなかった。。。フェイルオーバーが発生しないです。
試行錯誤の結果、著者提供の学習環境2=Vagrantでのマルチノード上でも試したのですが、同じところで詰まりました。
liberatorとして動かすpodのログを見ると、main.pyの中で異常を検出したノードを切り離す処理のところで
TypeError: delete_node() takes exactly 2 arguments (3 given)
が発生して、pod自身がCrashLoopBackOffで落ちてしまいます。
ココのメモは、同じレベルでエラーになるところににたどり着くまでの記録です。
とりあえず、実行例と実行順序が違いますし、mysqlのpodを実行するコマンドの案内もありません。
確認系だけの実行例を省略すると、
実行例11でビルド、実行例13でリポジトリへのpush、実行例16でrbac関係をapply、実行例17でdaemonsetのapply、です。
実行例11では、またdocker builがpipの古さでコケますので
DockerFileの中で、
「RUN pip install kubernetes」
の前に、
「RUN pip install --upgrade pip」
を追加します。
ビルド時にプライベートリポジトリに向けた名前にするのを忘れずに。
実行例16は、docker buildした一つ上のディレクトリからkubectlコマンドで、k8s-rbacディレクトリ配下のymlを一括でapplyします。
「daemonset.yml」の中に、先程ビルドしたイメージを指定するところがありますので、コレを編集してから、
kubectl apply -f daemonset.yml
を実行します。
で、この冒頭に書いた通り、エラーで落ちるのでフェイルオーバーが走らない、で終了です。
Step13 イングレス
グローバルIPを使ってNAT&ロードバランシングするイングレスの演習なのですが、学習環境1のMinikubeベースの説明のみで、学習環境2のマルチノード構成については、
一方、オンプレミス にK8sクラスタを構築する場合には、公開用IPアドレス(以下、VIP)をノード間で共有する機能を追加する必要があります。この機能を実装する方法として、kube-keepalived-vipがGitHubに公開されています。
で、そのkube-keepalived-vipに触れるのは「13.8 kube-keepalived-vipによるVIP獲得とHA構成」まで飛びます。
ただ、読者の中には、「クラウドサービスや商用のソフトウェア製品を利用するので、そのような知識は不要」と思う方もいるでしょう。また、この「Step13」を参考にして、自力でイングレスのHA構成をオンプレミス環境に構築するエンジニアは少ないかもしれません。しかし、自身で苦労して構築しないまでも、本書での疑似体験は、読者のスキルアップに必ず貢献すると筆者は確信しています。
とのことです。
実際、AWSでやるならaws-alb-ingress-controller使うとか、そんな話っぽいし。
13.8 kube-keepalived-vipによるVIP獲得とHA構成
実行例13で、いきなりエラーになります。バージョンの違いで「vip-daemonset.yml」と「ing-default-backend.yml」ファイルに記述する「apiVersion」が違うためのようです。
ここは、それぞれのファイルの冒頭にある
「apiVersion: extensions/v1beta1」
を
「apiVersion: apps/v1」
に書き換えます。
それでも、起動すると
error: error validating "ingress-keepalived/vip-daemonset.yml": error validating data: ValidationError(DaemonSet.spec): missing required field "selector" in io.k8s.api.apps.v1.DaemonSetSpec; if you choose to ignore these errors, turn validation off with --validate=false
と怒られます。
で、一応バリデーション無視のオプション「--validate=false」をつけて実行してみると、
The DaemonSet "kube-keepalived-vip" is invalid: spec.template.metadata.labels: Invalid value: map[string]string{"name":"kube-keepalived-vip"}: `selector` does not match template `labels`
ってなりますね。
今の自分にコレを解決する力はない。。。
k8s界隈でよく聞く「バージョン進化についていくのツライ」を目の当たりにした感じ。
もちろん、筆者提供のVagrantで立てた環境では、何の問題もなく実行できます。
13.9 パブリッククラウドのイングレス利用
IKSベースの説明+GKEについて一言、な内容です。
このレベルのことをAWSベースでやる場合EKS使うだろうから、「Kubernetes on AWS~アプリケーションエンジニア 本番環境へ備える」とかで学べればいいか。(書いてあるかは知らない)
Step14 オートスケール
14.2 学習環境3でのオートスケールの体験
学習環境3=IKS/GKSが前提の記述で、学習環境2=ローカルVirtualBoxでのマルチノード環境については
学習環境2でメトリックスサーバーを稼働させるには、少々手間が掛かります。
で飛ばされてしまうのが残念。
で、その「手間がかかる」やつ、頑張ってみました。(全く本書に書いてないところ)
メトリックスサーバーのgithub(https://github.com/kubernetes-sigs/metrics-server)
を見て
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.3.6/components.yaml
を実行しても、メトリックスは取れませんでした。
で、
Kubernetesクラスタでmetrics-serverを導入してkubectl topやHPA(Horizontal Pod Autoscaler)を有効にする - Qiita
を参考にしますが、
WARNING: You should no longer use manifests from master branch (previously available in deploy/kubernetes directory). They are now meant solely for development.
(超訳:masterのmanifest使うな!)
という超落とし穴があるので、git cloneではなく、releaseの方からtar.gzをwgetして解凍して使います。
(参考にしたQiitaの記事は0.3.3をダウンロードしているのですが、ここでは0.3.6を使います。)
さらに、
metrics-server-deployment.yaml
の「imagePullPolicy」の下に
command: - /metrics-server - --kubelet-insecure-tls - --kubelet-preferred-address-types=InternalDNS,InternalIP,ExternalDNS,ExternalIP,Hostname
を追加して、「kubectl apply -f .」でmetrics-server-0.3.6/deploy/1.8+の下のファイルを一括適用します。
イメージが0.3.7だと動かないらしく、あくまで0.3.6が必要なようです。
なので、最新が0.3.7だからといって最初に0.3.7のtar.gzを取ってきてしまった私は、ImagePullbackOffでPodが起動しきれない状態にハマりました。
「kubectl top node」で情報が欠落なく表示されればOKです。
ここまで進んだら、ようやく実行例4に取り掛かります。
docker buildはいつものように、自分のリポジトリを向くように修正しつつ。
「autoscale.yml」もimage指定を自身のリポジトリに向けるように修正が必要ですが、ここ、微妙にフォーマットが崩れているので、本書のファイル4の解説の通りに開始位置をそろえてあげましょう。
で、実行例9まで順調に進めて、負荷をかけてオートスケールを発動される様子を眺めていたのですが、
--cpu-percent=50 --min=1 --max=10
でオートスケールを定義しているのに対して、podが8つまでオートスケールしたところで、CPU使用率が50%を下回って安定します。
試しに、さらに負荷発生podをもう1つ立ててみたら、10までスケールしました。
10 pod 76%、で安定した状態になりました。
負荷を緩めた後のスケールインの方も、ちゃんと確認できました。
あれ?学習環境3=IKS or GKEじゃなきゃいけない理由ってなんだったんだ?メトリクスサーバー建てなくても、メトリックスが取れるってことだったのかな。
Step15 クラスタの仮想化
実際の演習は「15.9 環境構築レッスン」からで、そこまでは、環境の違いこそあれど、一応確認できます。
演習は、学習環境2または3で、となっています。
ただ、これよく読まないと気づかないんですけど、「クラスタ構築担当」「システム運用担当」「アプリ開発担当」という3種類の利用者が登場して、それぞれの端末上に設定をもたせる演習になっています。
それに気づかずに、これまで通りにずっとmasterノード上でコマンド打っていると、環境がグッチャグチャになって期待した結果が得られません。
そして、そんなキレイに環境をわけられなかったので、今回は断念です。
(EC2をさらに数台立てるだけで良かったのかもしれませんが、力尽きました。)
とても実用的な内容だと思うので、「こんなことが書いてあったな」ということだけは押さえておいて、必要になったときに参照することになるんじゃないかと思います。
実行例39で「rest-server.test」「rest-server.prod」にcurlで接続テストするのが出てくるんですが、そんなサービス立てたっけ?っていうのはちょっと謎。
最後に
繰り返しになりますが、このエントリは、著者が丁寧にgithub上に環境事項構築用Vagrantfileを公開してくれているにも関わらず、自らそれを拒んで環境を立てて、逐一ハマって苦しんだ記録です。
なので、別に本が悪かったわけじゃないのに、ちょっとネガティブなトーンのエントリになってますが、解説1つ1つはとても丁寧で、自分のような初めてkubernetesを触る人に必要十分な情報を提供してくれる、素晴らしい本でした。
そういえば、先日、コンテナアーキテクチャの導入がようやくキャズム越えが見えてきたというニュースがありました。
www.publickey1.jp
キャズム云々を騒いでいる段階は、逆に言えばまだ半数以上が未導入なわけで、コンテナアーキテクチャにこれから触れる人も多いかと思います。
これから着手する人に、是非推していきたいと思います。
著者の高良さん(@MahoTakara)、出版元のリックテレコムさん、良い本を出してくださり、本当にありがとうございまいた。
15Stepで習得 Dockerから入るKubernetes コンテナ開発からK8s本番運用まで (StepUp!選書)
- 作者:高良 真穂
- 発売日: 2019/09/27
- メディア:単行本(ソフトカバー)
また、行き詰まっているときにTwitter上で助けてくださった、青山さん(@amsy810)、こばさん(@tzkb)、本当にありがとうございました。
まさか、こちらの世界の神のような人に直接助けてもらうことになるとは思わず、とにかく恐縮です。