[{"content":"そとちゃん 2025年11月14日 闘病生活が続く日のお昼。\n突然そとちゃんが荒い口呼吸をはじめた。\n少しショッキングな映像なので折りたたみ その時が来たか、と思った。\n(病院の先生からこういった症状が出ると危険だと聞いていた)\nある程度覚悟はしていたが、\n1人だとおかしくなりそうだったのですぐに病院へ。\n病院に着く頃には呼吸が少し落ち着いており、\n検査してもらったが酸素飽和度に問題がないので苦しんではいないようだった。\n呼吸が乱れた原因が腫瘍によるものか分子標的薬の副作用かはもうわからないので、\nひとまず投薬や給餌は中止、点滴のみやろうということになった。\n入院の選択肢もあったがこの日は一旦帰宅。\n帰宅すると急に動き出すそとちゃん。\nさっきまでぐったりしていたのが嘘みたいに、\n家の中を動き回る。\n今思えば、これがエンジェルタイムというものだったのだろう。\nそとちゃんは夜までこちらが心配になるくらい活発に動き回った。\nふらつきながら、でも力強く、\n自分のナワバリをしっかりと調べていた。\n夜になるとすこし落ち着いたが、\nまた少し呼吸が荒くなってしまった。\n少しショッキングな映像なので折りたたみ たくさん撫でると少し呼吸も楽になって、\nお気に入りのベッドで安心した様子。\n「また明日元気になってたくさん遊ぼうね、おやすみ」\n日付がまわった朝2時半、そとちゃんに語りかけて俺は眠りについた。\n2025年11月15日 次の日の早朝5時。\n休日で普段ならまだ寝ているのに、\nこの日はやけに早く目が覚めた。\n二度寝前に台所で水を一杯飲み、\nそとちゃんの様子を確認する。\n寝ているところに近づくとすぐ起きてしまうそとちゃんだが、\nこの日は目覚めることがなかった。\n涙がとまらなかった。\nたくさん感謝の言葉をかけた。\n謝罪の気持ちもあったけど、それよりも感謝を伝えたかった。\n少し落ち着いてから、\n正式な診断とその後の処置をしてもらうためにいつもの病院へ。\n早朝だったのでいつもと違う先生、看護師さんだったが、\nとても優しい言葉をかけていただいた。\n「最後まで本当に素敵なねこちゃんでしたね、がんばりましたね」\nそういった言葉でそとちゃんと自分をたくさん褒めてもらった。\nその度に涙が出た。\n自分を責めることがないように言葉を選んでくれたのだとは思うが、\nただただ嬉しかった。\n亡くなる瞬間に立ち会えなかったのでこれは本当なのか先生の優しい嘘かはわからないが、\n状況から窒息ではなく体の機能がすーっと止まったような形で、\n最後も苦しまずに逝けたのではないか、とのことだった。\nそれなら安心だし、そうであってほしい。\n2025年11月15日 朝5時 推定9歳6ヶ月\nそとちゃんは短い一生を生き切った。\nお別れ その後、\nたくさんの友人が手を合わせに来てくれた。\nたくさんの優しい言葉をいただいた。\nそとちゃんは本当に愛されていたんだな。\nそう実感するたびに涙が出た。\n本当はそとちゃんの身体もずっとこのまま残しておきたかったが、\n腐ってしまうのはかわいそうなので火葬することにした。\n小さな、本当に小さなお葬式をした。\n最後は体重2.3kgしかなかったが、元気な時でも最大4kgだったそとちゃん。\n棺も最小サイズだった。\nそとちゃんを炉に入れる直前はもう、 涙どころか鼻水もよだれも止まらなかった。\nあんなに人前で取り乱したことはないんじゃないかというくらい、\nどうしようもなくなってしまった。\nたくさん喚いて少し落ち着いたころ、\nそとちゃんのお骨が返ってきた。\n頭以外は健康そのものだったので状態は良く、\n立派な骨になって返ってきた。\n骨になってもかわいいなんて、\nやっぱりそとちゃんはすごいねこだ。\nあまり大っぴらに見せるものでもないので折りたたみ 式場の方にも優しい言葉をかけていただき再び号泣。\n本当に泣いてばかりだった。\nお骨は埋葬せず、\nしばらくは家で一緒にいることにした。\nふるさと お葬式を終えてしばらく経ち、年も明けたころ。\nそとちゃんを保護してくださった群馬の保護主さんのところを尋ねた。\nhttps://www.larcenchat.com/\n保護主さんとお会いするのはそとちゃんがうちに来た日以来で、\nたくさんお話をさせていただいた。\n信じてそとちゃんを託してくれたのに長生きさせてあげられなかった、\n謝罪の気持ちを伝えたかったが、\nそれよりも先にたくさんの優しい言葉をいただいた。\n言葉の一つ一つが本当に嬉しかった。\nそとちゃんの後輩となる保護猫ちゃんたちとも遊ばせてもらった。\nねこちゃんに触れるのも数ヶ月ぶりだったので、\n久しぶりに暖かい気持ちになれた。\nカフェで素敵な時間を過ごした後、\nそとちゃんが保護された場所を訪問した。\n本当に静かで、のんびりとした場所だった。\nこういうところで生まれ育ったからあんなに優しいねこになったのかな、\nなんて考えたりした。\nそとちゃんはこの場所で、産気づいた状態で保護された。\nそとちゃんの子かはよくわかっていないが、保護される前は別の複数の子猫と一緒にいたところも目撃されていたらしい。\n(その後保護主さんのところで2匹出産している)\nそとちゃんはもういないけど、\nそとちゃんの生きた証はこの世界に残っている。\nそう考えると嬉しいし、少し寂しさが薄れた気がした。\nありがとう そとちゃんがうちに来てくれてからの6年半、\n本当に幸せな毎日だった。\n結局一度も「シャーッ」「フーッ」と怒ることのなかった、心のやさしいねこだった。\nおもちゃ遊びは好きだけど、\nそれよりもごろごろするのが好きだった。\nイタズラなんてほとんどしないおりこうさんで、\n人間よりも家をきれいに使ってくれた。\n一緒にいるとたのしくてあたたかくて、\nとにかく笑顔にさせてくれた。\n今もスマホのカメラロールを辿ればそとちゃんの姿がたくさん残っていて、\n一緒に過ごした日々を思い出して幸せな気持ちになれる。\nうちに来てくれてありがとう。\nごはんの好き嫌いはあったけど、\nおいしそうにおやつを食べてくれてありがとう。\nひざに乗ってくれてありがとう。\n一度乗ると全然動かないから脚がしびれたけど、幸せだった。\n撫でさせてくれてありがとう。\nブラシが嫌いなのに毛並みはいつもきれいだった。\nたくさんおしゃべりしてくれてありがとう。\n俺はねこの言葉がわからないしそとちゃんも人間の言葉がわからないけど、\n毎朝2人でおしゃべりするのがたのしかった。\n自慢の長いしっぽを揺らしてくれてありがとう。\nピンクの肉球を触らせてくれてありがとう。\n最後まで喉を鳴らしてくれてありがとう。\n初めて一緒に暮らしたねこがそとちゃんで本当によかった。　世界一の最高のねこでした。\n","date":"2026-05-01T00:00:00Z","image":"/post/2026-05-01-sotochan/sotochan.JPG","permalink":"/post/2026-05-01-sotochan/","title":"そとちゃん"},{"content":"そとちゃん 食欲不振 9月末ごろから食欲不振でごはんをあまり食べなくなったそとちゃん。\n10月に入ってからは好きだったおやつもほとんど食べなくなってしまった。\nお水は自分から飲んでくれるけど、\n食べ物には全然反応しない\u0026hellip;\n何日か水しか飲まない状態が続き、\nお腹が空きすぎて吐く?ようなことがあったので再び病院へ。\n当日の体重は2.9kg。\n1週前に病院で計ったときが3.22kgだったので、\nわずか7日で300g近く落ちてしまったことになる。\n超音波検査の結果、胃腸炎の疑いが出た。\n9月後半に追加したビクタスが少し強い薬なのでそれが影響した可能性が高いとのこと。\nさらに前回追加で行った細菌検査の結果が返ってきていて、\n弱い方の薬リレキシペットがそとちゃんの耳漏から採取した菌に効果があることがわかった。\n胃が荒れる原因になっている可能性があり、\n弱い薬でも十分効く見込みがあるとのことで、\n吐き気止めのお薬を追加しビクタスは休薬することに。\n胃腸炎の他に腎臓の悪化も影響しているかも?とのことなので、\nさらに点滴も入れてもらった。\nひとまずこれで様子を見て、\nそれでも改善しないようなら点滴の頻度を増やすことになった。\n強制給餌 病院から戻って数日経っても症状は改善せず\u0026hellip;\n吐くことはなかったが、\n相変わらずお水を飲むだけでごはんを全く食べない状態が続いた。\nなにもお腹に入らない状況は流石にまずいので、\nこの頃から強制給餌を始めた。\nドライフードを小さく割り、\nお薬の錠剤と同じ要領で上を向かせた状態で口を開けて飲み込ませる。\n9月末からお薬はこの方法で飲ませていて、\nそとちゃんも俺も割と慣れていたのでスムーズに飲ませることができた。\nが、問題はその量。\nドライフードを飲ませられる量は一回で高々1,2粒なので、\n必要なエネルギー(カロリー)は全然足りない。\nそとちゃんの負荷もしんどそうで、\nどうしよう\u0026hellip;となり、点滴も兼ねて再び病院へ。\n点滴 この日は血液検査とエコー検査をしてもらった。\n血液検査の結果、\n腎臓の数値は9月に行った猫ドック時とあまり変わらず少し高めの数値で、\n悪くなってはいないが良くもなっていないという状況。\nエコー検査では強制給餌したドライフードがお腹の中でうまく消化できず残っていることが確認された。\n胃腸炎は治っていそうだが、\n何日間かごはんを食べていなかったのもあり、\n消化器自体の働きがあまりよくない様子。\nこの日の体重は2.8kgで、\nたった数日で100gも落ちてしまっていた。\n先生に強制給餌の件を相談し、\nより高カロリーで消化器への負担も少ないロイヤルカナンの流動食を与えることになった。\n流動食を入れたシリンジを口の横から差し込み、\n吐かないように少しずつ入れていく。\n嫌がるなのは変わらないが、\nドライフードよりはあげやすそうになってよかった。\nとはいえ自力で食べてくれることが一番なので、\nこの日は耳に塗る食欲増進剤を追加してもらい、\nさらに腎疾患の疑いを見て毎日点滴に通うことになった。\n食欲復活 \u0026hellip;が。\nその日の夜にいつも通りダメ元でおやつを出したところ、\n急にガツガツ食べ始めた!\n自分から食べるなんて本当に久しぶり。\nその後は朝のおやつも完食。\n超えらい。\n点滴の効果が出るには早すぎるということで、\nおそらく食欲増進剤が効いたのか\u0026hellip;?\nとはいえ腎臓の数値は心配なので、\n先生とも相談して念の為毎日の点滴は継続。\nいいかんじ この頃のそとちゃんは絶好調。\nおやつメインではあるものの自分から食べてくれるようになり、\n強制給餌の割合も減っていった。\nたくさん食べたおかげで、体重も2.95kgまで復活。\n毎日の点滴通いもがんばってくれて、\n4日目には脱水症状もだいぶ改善していたので頻度を週1に減らせることになった。\n前庭疾患 食事の問題は改善したものの、\n一番の問題である前庭疾患がなかなか良くならない。\n首の傾きはだんだん角度がきつくなっているように見えるし、\nやっぱり左耳が痛いのかな\u0026hellip;?\nこの頃から鳴き声も少し変になってきた。\n前は高い声でかわいくおしゃべりしてくれていたのだが、\n若干低めでダミ声のような鳴き方をするようになった。\n(近づくと爆音ゴロゴロするのは変わらず。かわいい)\nまた、先生が診察中に気付いたのだが、\n顔面の左側(正面から見て右側)が麻痺しているようだった。\n瞬膜がちゃんとひっこんでいることもあるのだが、\n出っ放しのときは見た目がかわいそうで、\nこれが個人的にはかなりつらかった。\n流石に左目が見えづらいとそとちゃんも気になるのか、\n今まで目ヤニが出やすかったりで閉じがちだった右目が逆にぱっちり開くようになった。\n(諦めずに目薬を頑張ってきてよかったね\u0026hellip;)\nこんな感じで、\n前庭疾患の症状は良くなるどころか少しずつ悪くなっているようだった。\n二次診療 そんなこんなで投薬と通院が続く中、\n先生から脳神経科での二次診療(CT/MRI)の打診をいただいた。\n食欲不振と脱水症状はかなり改善した。\n抗生剤も細菌検査を実施した上で1ヶ月以上続けている。\nなのに症状が改善しない。\nこれは本当に細菌性の中耳炎なのだろうか？\n何か別の原因があって、耳漏や菌はその影響ではないだろうか？とのことだった。\n9月に最初の発作が出た時に実は「腫瘍性の問題も一応考えられる」というお話を受けていたが、\n症状から最も当てはまるのが中耳炎ということで先生も俺も治療を続けてきた。\nでもこの状況では抗生剤を続けても改善が見込めない。\n一度腫瘍性の問題を考え直すべきではないか。\n正直、考えたくなかった。\n抗生剤を続けていれば中耳炎は治り、\n元のそとちゃんに戻ると信じてお互い嫌な思いをしながら頑張ってきたのに。\nでも、このまま症状が治らずそとちゃんが居なくなる、なんてのはもっと嫌だった。\n今の治療で治る見込みが薄いなら、他にできることを見つけたい。\nそんな思いで、日本動物高度医療センター(JARMeC)での検査を受けることにした。\n当日病院に着いて、まずはレントゲン検査と血液検査。\nレントゲン検査の結果はあまり良くなく、\n9月の猫ドックでも確認したように耳の奥の骨が溶けているのに加え、\n耳の穴から内耳までの経路が何か(膿?腫瘍?)で塞がれてしまっているようだった。\n血液検査の結果は腎臓の数値が基準値を超えていて、\nそとちゃんの体力が落ちていることを考慮すると全身麻酔をかけての検査にはかなりリスクがある状態。\nとはいえ、これは事前にかかりつけの先生とも相談した上で決めていたし、\n何よりもそとちゃんの生きる力を信じてサインすることにした。\nそとちゃんを預けて半日待機。\nあまり生きた心地はしなかった。\n「やっぱり中耳炎でした」となることだけを祈って待っていた。\n受け取りの時間が来て、\nそとちゃんは無事に麻酔から目が覚めていた。\nまずは無事に意識が戻ってよかった。\nほんとうにがんばってくれて、すごいねこだ。\nCT検査の結果は、\n悪性腫瘍(がん)の疑いが強いとのことだった。\n耳から第一頚椎にかけて病変が広がっており、頭蓋骨も溶かして脳に到達しているような状態。\n中耳~前庭にかけて組織が広範囲に壊れていて、神経疾患の原因はこれで間違いない。\n実際の病変を採取して細胞診検査に回したが、\nこの段階で確定はできないものの炎症や白血球の増大が見られないため、\n膿ではなく腫瘍の可能性が非常に高い。\n大きなくくりでの症状としては中耳炎+前庭疾患に違いないが、\nその原因が細菌性である可能性はほぼゼロ、ほぼ確実に腫瘍に由来するものだろう。\n細胞診検査の結果待ちではあるものの、\n悪性腫瘍であった場合はすでに脳の広範囲に広がっていることから、\n物理的な切除は難しい。\n余命は残り数ヶ月との宣告を受けた。\n無理やりメモから書き起こしてはいるが、\nこのとき自分が何を考えていたか、あまり覚えていない。\n病院からの帰り道、十数年ぶりに声を上げて泣いたことだけは覚えている。\n緩和ケア 二次診療の結果を受けて、かかりつけの先生と相談。\n痛みと炎症緩和のため、ステロイド系のお薬を追加し、\n抗生剤の量は減らすことになった。\nリレキシペットは飲む回数が多く負担がかかるので、休薬していた一日一回のビクタスを再開。\n通院も負担がかかるので、これまで通いで行っていた点滴を家で実施。\nとにかくそとちゃんが残りの時間を穏やかに過ごせるためにやっていこうということになった。\nもう総合栄養食と一般食とかを気にする段階でもないので、\n食べられるうちは大好きなおやつをたくさん食べさせてOK。\nニンゲンは悲しいけど、ねこは嬉しいね\u0026hellip;\n歩行の衰えにも備えて、\nトイレを変えたり、ペット用ステップを追加したり。\n念の為つけていたペットサークルもそとちゃんが寂しい思いをしないように撤去。\nそとちゃんと一緒に過ごす時間を最優先にした。\n強制給餌再び そとちゃんの緩和ケアを始めて、11月になった頃。\nだんだんとおやつも残すことが増えてきて、\nついにちゅーるすら食べなくなってしまった。\nこれまで効果があった食欲増進剤を試しても効果が出ず、\n強制給餌を再開することになった。\n神経症状で口をあけるのも辛くなってきたのか、\n以前よりも嫌がるように。\n頑張って飲んでくれるけど、その後は毎回ふきげん。\nこれまではごほうびのおやつでごまかせたけど、\nそれができないのでどうやって機嫌を直したらいいかもわからない。\nとにかく給餌の後はたくさん褒めて、\n逃げたときも無理に追わずに放置するようにした。\nそとちゃんが嫌がっているのは明らかだったが、\n命を少しでも繋ぐために食べさせるしかなかった。\n細胞診検査の結果 強制給餌を再開して少したった頃、二次診療で行った細胞診検査の結果が届いた。\n確定診断の結果は悪性上皮性腫瘍。\nそとちゃんががんを患っていることが確定した。\nあまりにも広範囲に広がっているため、\n由来(耳垢腺がん/唾液腺がん/扁平上皮がん)の特定はできなかったそう。\n再度かかりつけの先生と相談。\nがんに対する治療をするのか、\nそれはせずにこのままステロイドの投与を続けるのか。\nリスクを説明してもらった上で、\n自分は抗がん剤(分子標的薬)での治療を選択した。\n外科手術は不可能、放射線治療もそとちゃんの体力を考慮すると非現実的。\nそんな状態でも腫瘍による神経の圧迫や耳漏を少しでも和らげられれば\u0026hellip;と思っての判断だった。\nこの時点でもう完治を目指すつもりはなく、\nあくまで楽にしてあげるための抗がん剤。\n分子標的薬は一般的な抗がん剤に比べると効果もリスクも抑えめとのこと。\n1,2回試してなにか苦しそうであればすぐやめてステロイドのみに切り替える。\nそう決めて投与することにした。\n抗がん剤 1回目の投与。\n思ったよりも錠剤が大きく、\n飲ませるのが大変だった。\n初回なので投与後は2日程度様子を見る。\n副作用が出た場合は下痢や嘔吐が出るはずだが、\nそのような兆候は見られなかった。\n残りの時間 そとちゃんは何を考えていただろうか。\n自分に残された時間を理解していたかはわからないが、\n強制給餌や抗がん剤治療が始まってからも毎日これまでと同じく、\nたくさん寝て、\nたまに起きて風呂場の水を飲み、\n暖かいところを探してもうひと眠り。\nまた起きては爪を研ぎ、\nお気に入りの紙袋にもぐって過ごした。\n毎日楽しく、好きなことをたくさんして1日1日を大切に過ごしていた。\n(つづく)\nおまけ ","date":"2026-02-08T00:00:00Z","image":"/post/2026-02-08-sotochan/sotochan.jpeg","permalink":"/post/2026-02-08-sotochan/","title":"10月と11月のそとちゃん"},{"content":"まとめ セルフホスト版Dify(Community)で、非公開?のコンソールAPIがある v1.9.2から、ログイン時に取得できるトークンの扱いが変わった トークンをこれまでのレスポンスボディでなくcookieに入れて返してくるのでそのまま使うのが良い # login API: https://github.com/langgenius/dify/blob/1.9.2/api/controllers/console/auth/login.py#L44 email=\u0026#34;hogehoge@uzimihsr.com\u0026#34; # Difyアカウントのメールアドレス password=\u0026#34;xxxxxxxx\u0026#34; # Difyアカウントのパスワード base_url=\u0026#34;http://localhost\u0026#34; # localhost以外で立ててる場合はそれに合わせて変更 curl -c cookie -X POST \u0026#34;${base_url}/console/api/login\u0026#34; -H \u0026#39;Content-Type: application/json\u0026#39; -d \u0026#34;{\\\u0026#34;email\\\u0026#34;:\\\u0026#34;${email}\\\u0026#34;,\\\u0026#34;password\\\u0026#34;:\\\u0026#34;${password}\\\u0026#34;}\u0026#34; csrf_token=$(grep csrf_token cookie | cut -f7) #　profile API: https://github.com/langgenius/dify/blob/1.9.2/api/controllers/console/workspace/account.py#L97 curl -sSf -X GET -b cookie -H \u0026#34;X-CSRF-Token: ${csrf_token}\u0026#34; \u0026#34;${base_url}/console/api/account/profile\u0026#34; 環境 Dify v1.9.2 docker composeで起動 経緯 Difyの管理用APIであるConsole APIは公式ドキュメントで公開されていないもののなかなかに便利で、\nユーザー、ワークスペース、モデルプロバイダー、ツール、アプリなどの管理をUIでなくAPIで実行できるのでDify運用の自動化に使えそう。\n一応GitHubのリポジトリを確認すると、このあたりで各種APIの中身が確認できる。\nhttps://github.com/langgenius/dify/tree/main/api/controllers/console\n実際に使われている偉大な先駆者様達の例↓\nhttps://zenn.dev/furunag/articles/dify-cli-onefile-bash https://zenn.dev/zozotech/articles/42ddb735d9f3da ところが、\n最近セルフホスト版Difyのバージョンを最新の1.9.2に上げたところlogin API(POST /console/api/login)でAPIを呼ぶためのトークンが取れなくなってしまった。\n(偉大な先駆者様達のコードも1.9.2ではおそらく動かなくなっている)\n$ # login API: https://github.com/langgenius/dify/blob/1.9.2/api/controllers/console/auth/login.py#L44 $ # アカウントのメールアドレス(email)とパスワード(password)をJSONでPOSTするとログインできる $ email=\u0026#34;hogehoge@uzimihsr.com\u0026#34; # Difyアカウントのメールアドレス $ password=\u0026#34;xxxxxxxx\u0026#34; # Difyアカウントのパスワード $ base_url=\u0026#34;http://localhost\u0026#34; # localhost以外で立ててる場合はそれに合わせて変更 $ curl -X POST \u0026#34;${base_url}/console/api/login\u0026#34; -H \u0026#39;Content-Type: application/json\u0026#39; -d \u0026#34;{\\\u0026#34;email\\\u0026#34;:\\\u0026#34;${email}\\\u0026#34;,\\\u0026#34;password\\\u0026#34;:\\\u0026#34;${password}\\\u0026#34;}\u0026#34; {\u0026#34;result\u0026#34;:\u0026#34;success\u0026#34;} $ # レスポンスボディにアクセストークンが入ってない! 困ったのでちょっと調べてみた。\nソースコード調査 結論から言うとこれ。\nhttps://github.com/langgenius/dify/releases/tag/1.9.2 https://github.com/langgenius/dify/pull/24365 v1.9.1まではlogin APIがアクセストークンをレスポンスボディ(.data.access_token)で返していたのが、\nセキュリティ向上を目的にHttpOnly Cookieに埋め込んで返すようになったらしい。\ncurlの-vオプションで実際にCookieの中身を確認してみる。\n$ curl -v -X POST \u0026#34;${base_url}/console/api/login\u0026#34; -H \u0026#39;Content-Type: application/json\u0026#39; -d \u0026#34;{\\\u0026#34;email\\\u0026#34;:\\\u0026#34;${email}\\\u0026#34;,\\\u0026#34;password\\\u0026#34;:\\\u0026#34;${password}\\\u0026#34;}\u0026#34; Note: Unnecessary use of -X or --request, POST is already inferred. * Host localhost:80 was resolved. * IPv6: ::1 * IPv4: 127.0.0.1 * Trying [::1]:80... * Connected to localhost (::1) port 80 \u0026gt; POST /console/api/login HTTP/1.1 \u0026gt; Host: localhost \u0026gt; User-Agent: curl/8.7.1 \u0026gt; Accept: */* \u0026gt; Content-Type: application/json \u0026gt; Content-Length: 52 \u0026gt; * upload completely sent off: 52 bytes \u0026lt; HTTP/1.1 200 OK \u0026lt; Server: nginx/1.23.1 \u0026lt; Date: Sat, 25 Oct 2025 07:30:12 GMT \u0026lt; Content-Type: application/json \u0026lt; Content-Length: 20 \u0026lt; Connection: keep-alive \u0026lt; Set-Cookie: access_token=eyJhxxxxxxxxxxxx.eyJ1c2xxxxxxxxx.VzvJKdUxxxxxxxx; Expires=Sat, 25 Oct 2025 08:30:12 GMT; Max-Age=3600; HttpOnly; Path=/; SameSite=Lax \u0026lt; Set-Cookie: refresh_token=a4ac94922exxxxxxxxxx; Expires=Mon, 24 Nov 2025 07:30:12 GMT; Max-Age=2592000; HttpOnly; Path=/; SameSite=Lax \u0026lt; Set-Cookie: csrf_token=eyJhbxxxxxxxx.eyJleHAixxxxxxx.uSNFW3Fxxxxxxxx; Expires=Sat, 25 Oct 2025 08:30:12 GMT; Max-Age=3600; Path=/; SameSite=Lax \u0026lt; X-Version: 1.9.2 \u0026lt; X-Env: PRODUCTION \u0026lt; * Connection #0 to host localhost left intact {\u0026#34;result\u0026#34;:\u0026#34;success\u0026#34;} たしかにHttpOnlyなCookie(access_token, refresh_token)にトークンが埋め込まれている。\nうまくいかない じゃあCookieを使えばいいじゃん、ということで-cオプションでそのままCookieを保存して投げてみる。\n$ # cookieをファイルに保存 $ curl -c cookie -X POST \u0026#34;${base_url}/console/api/login\u0026#34; -H \u0026#39;Content-Type: application/json\u0026#39; -d \u0026#34;{\\\u0026#34;email\\\u0026#34;:\\\u0026#34;${email}\\\u0026#34;,\\\u0026#34;password\\\u0026#34;:\\\u0026#34;${password}\\\u0026#34;}\u0026#34; {\u0026#34;result\u0026#34;:\u0026#34;success\u0026#34;} $ # profile API: https://github.com/langgenius/dify/blob/1.9.2/api/controllers/console/workspace/account.py#L97 $ # 自分のアカウント情報を確認できる $ curl -X GET -b cookie \u0026#34;${base_url}/console/api/account/profile\u0026#34; {\u0026#34;code\u0026#34;:\u0026#34;unauthorized\u0026#34;,\u0026#34;message\u0026#34;:\u0026#34;CSRF token is missing or invalid.\u0026#34;,\u0026#34;status\u0026#34;:401} $ # が、失敗... だめ。\n再挑戦 トークン周辺のコードをもう少し調べる。\nhttps://github.com/langgenius/dify/blob/1.9.2/api/libs/login.py#L79 https://github.com/langgenius/dify/blob/1.9.2/api/libs/token.py#L171\nどうやらX-CSRF-Tokenヘッダにcsrf_token Cookieを与えないとだめっぽい。\n(login APIが返すCookieのうち、これだけHttpOnlyじゃないのでJavaScriptでも触れる)\nCSRFトークンってこうなってんだね\u0026hellip; ということでもう一度試してみる。\nCookieを直接触ってしまっているが、\n上記の通りcsrf_tokenに限りHttpOnlyじゃないのでこれは実際のUIの挙動と同じはず。\n$ # cookieをファイルに保存 $ curl -c cookie -X POST \u0026#34;${base_url}/console/api/login\u0026#34; -H \u0026#39;Content-Type: application/json\u0026#39; -d \u0026#34;{\\\u0026#34;email\\\u0026#34;:\\\u0026#34;${email}\\\u0026#34;,\\\u0026#34;password\\\u0026#34;:\\\u0026#34;${password}\\\u0026#34;}\u0026#34; {\u0026#34;result\u0026#34;:\u0026#34;success\u0026#34;} # cookieファイルの中のcsrf_token(not HttpOnly)の値を取得 $ csrf_token=$(grep csrf_token cookie | cut -f7) $ #　profile API: https://github.com/langgenius/dify/blob/1.9.2/api/controllers/console/workspace/account.py#L97 $ # 今度はX-CSRF-Tokenを添えて... $ curl -sSf -X GET -b cookie -H \u0026#34;X-CSRF-Token: ${csrf_token}\u0026#34; \u0026#34;${base_url}/console/api/account/profile\u0026#34; | jq { \u0026#34;id\u0026#34;: \u0026#34;932a9004-b67c-422d-b7aa-89b11d8e04b5\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;hogehoge\u0026#34;, \u0026#34;avatar\u0026#34;: null, \u0026#34;avatar_url\u0026#34;: null, \u0026#34;email\u0026#34;: \u0026#34;hogehoge@uzimihsr.com\u0026#34;, \u0026#34;is_password_set\u0026#34;: true, \u0026#34;interface_language\u0026#34;: \u0026#34;en-US\u0026#34;, \u0026#34;interface_theme\u0026#34;: \u0026#34;light\u0026#34;, \u0026#34;timezone\u0026#34;: \u0026#34;America/New_York\u0026#34;, \u0026#34;last_login_at\u0026#34;: 1761380052, \u0026#34;last_login_ip\u0026#34;: \u0026#34;172.30.0.1\u0026#34;, \u0026#34;created_at\u0026#34;: 1758760919 } $ # できた! やったぜ。\nDifyのConsole APIを呼び出して、ユーザー情報を取得することができた。\nおわり うまいこと使って、\nDifyで作ったアプリだけじゃなくてDify自体の管理も自動化できたらいいな〜。\n(というか公式でConsole API SDK出ないかな\u0026hellip;)\nおまけ ","date":"2025-10-25T00:00:00Z","image":"/post/2025-10-25-dify-console-api-curl/sotochan.jpeg","permalink":"/post/2025-10-25-dify-console-api-curl/","title":"Dify Console APIをcURLで呼び出してみた(v1.9.2 CSRF Token対応)"},{"content":"まとめ ある日PyPIからPyAV 10.0.0のwheelが消えてpip installがうまくいかないことがあった PyAVに限らず、PyPIで公開されているパッケージのwheelは削除されることがある どのパッケージも最新版はだいたいwheelが公開されているのでなるべく新しいバージョンを使おう! 昨日まで動いてたのに\u0026hellip; PyAV 10.*系に依存しているプロジェクトを開発していて、\nある日何気なく再インストール(pip uninstall \u0026amp; pip install)したらこれまでインストールできていたものが急に失敗してしまった。\n(DockerのPythonコンテナで実施: docker container run --rm -it python:3.10.12 /bin/bash)\n(tutorial-env) root@e199492ba090:/# pip install av==10.* Collecting av==10.* Downloading av-10.0.0.tar.gz (2.4 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.4/2.4 MB 17.7 MB/s 0:00:00 Installing build dependencies ... done Getting requirements to build wheel ... error error: subprocess-exited-with-error × Getting requirements to build wheel did not run successfully. │ exit code: 1 ╰─\u0026gt; [11 lines of output] Package libavformat was not found in the pkg-config search path. Perhaps you should add the directory containing `libavformat.pc\u0026#39; to the PKG_CONFIG_PATH environment variable Package \u0026#39;libavformat\u0026#39;, required by \u0026#39;virtual:world\u0026#39;, not found Package \u0026#39;libavcodec\u0026#39;, required by \u0026#39;virtual:world\u0026#39;, not found Package \u0026#39;libavdevice\u0026#39;, required by \u0026#39;virtual:world\u0026#39;, not found Package \u0026#39;libavutil\u0026#39;, required by \u0026#39;virtual:world\u0026#39;, not found Package \u0026#39;libavfilter\u0026#39;, required by \u0026#39;virtual:world\u0026#39;, not found Package \u0026#39;libswscale\u0026#39;, required by \u0026#39;virtual:world\u0026#39;, not found Package \u0026#39;libswresample\u0026#39;, required by \u0026#39;virtual:world\u0026#39;, not found pkg-config could not find libraries [\u0026#39;avformat\u0026#39;, \u0026#39;avcodec\u0026#39;, \u0026#39;avdevice\u0026#39;, \u0026#39;avutil\u0026#39;, \u0026#39;avfilter\u0026#39;, \u0026#39;swscale\u0026#39;, \u0026#39;swresample\u0026#39;] [end of output] note: This error originates from a subprocess, and is likely not a problem with pip. error: subprocess-exited-with-error × Getting requirements to build wheel did not run successfully. │ exit code: 1 ╰─\u0026gt; See above for output. note: This error originates from a subprocess, and is likely not a problem with pip. なんでやねん\u0026hellip;と思いながらもログを見てみると、\nなぜかsdist(av-10.0.0.tar.gz)をダウンロードしてwheelをローカルでビルドしようとしている。\n(そしてビルドに必要なFFmpegのパッケージが実行環境にないため失敗している)\nwheelはどこへ消えた? Python Packaging User Guideにも説明がある通り、\nそもそもローカルでwheelをビルドしようとしているのはpipが対象のwheelを見つけられなかったからである。\nWhen pip, the standard Python package installer, cannot find a wheel to install, it will fall back on downloading a source distribution, compiling a wheel from it, and installing the wheel. https://packaging.python.org/en/latest/discussions/package-formats/#what-is-a-source-distribution\nということは、これまではwheelが見つかる状態だったのにある日から対象のwheelが取得できなくなった？\n実際にPyPIでPyAV 10.0.0のFilesを確認すると、たしかにsdistだけ残っていてwheelが消えている。\nhttps://pypi.org/project/av/10.0.0/#files\n(ちなみにWayback Machineで過去のページをみるとwheelが公開されていた痕跡が\u0026hellip;)\nいきなりwheelが消えるなんて、そんなことある\u0026hellip;？\n\u0026hellip;\nありました。\n「PyAV 9.2.0のwheelがPyPIから消えたんだけど！(意訳)」というissueでのコメント。\nOld binary wheels were removed to have space for the 14.4.0 release.\nhttps://github.com/PyAV-Org/PyAV/issues/1906#issuecomment-2899577746\n「古いwheelは新しいバージョン(14.4.0)リリースの領域を確保するために削除した(意訳)」\nはえ〜\u0026hellip;\nそんなことあるんすね\u0026hellip;\n自分がPyPIに詳しくないので初めて知ったのだが、\nどうやらプロジェクトあたり最大10GBまでのサイズ上限があるらしい。\nPyPI imposes storage limits on the size of individually uploaded files, as well as the total size of all files in a project.\nThe current default limits are 100.0 MB for individual files and 10.0 GB for the entire project. https://docs.pypi.org/project-management/storage-limits/\nまあサイズ上限があるなら古いやつから消すよな\u0026hellip;\nソース(sdist)さえあればビルドできないわけじゃないし\u0026hellip; (自分の環境はダメだったが)\nちなみに「重要なパッケージはたとえOwnerであってもPyPI上からの削除を制限した方が良いのでは?」的な議論もあるっぽい。\n自分も使う側の人間なので気持ちはわかるが、開発者に責任を押し付けすぎでは？とも思う。\nOSSむずかしい\u0026hellip;。\nhttps://discuss.python.org/t/stop-allowing-deleting-things-from-pypi/\nあとはPyPIから削除されたwheelをアーカイブしているサイトもいくつかあるようだが、\n非公式でメンテナンスは期待できないだろうし、\nなにかあった時に自己責任なのであんまり触りたくないな〜という気持ち。\nhttps://dashboard.stablebuild.com/pypi-deleted-packages/\n素直にバージョンを上げよう じゃあどうすんねんという話だが、\n古いバージョンへの依存をやめれば良い。\nバージョン縛りをなくしたところ、あっさり成功。\nhttps://pypi.org/project/av/16.0.1/#files で公開されている最新版のwheelをダウンロード\u0026amp;インストールできた。\n(tutorial-env) root@6673fc741395:/# pip install av Collecting av Downloading av-16.0.1-cp310-cp310-manylinux_2_28_aarch64.whl.metadata (4.6 kB) Downloading av-16.0.1-cp310-cp310-manylinux_2_28_aarch64.whl (38.2 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 38.2/38.2 MB 21.9 MB/s 0:00:01 Installing collected packages: av Successfully installed av-16.0.1 (tutorial-env) root@6673fc741395:/# python Python 3.10.12 (main, Aug 16 2023, 08:07:04) [GCC 12.2.0] on linux Type \u0026#34;help\u0026#34;, \u0026#34;copyright\u0026#34;, \u0026#34;credits\u0026#34; or \u0026#34;license\u0026#34; for more information. \u0026gt;\u0026gt;\u0026gt; import av \u0026gt;\u0026gt;\u0026gt; やったぜ。\n\u0026hellip;というより、そもそも3年前のバージョンに依存して使ってる方が悪いな、これ。\n完全に自業自得。\nこれだけ古いと脆弱性も見つかってるだろうし、そこらへん自分で運用管理できてないのがバレバレ。\n自力でビルドもできないOSSフリーライダーはなにをやってもダメ。\nよいこのみんなはおとなしく提供元の意向にしたがって最新のバージョンを使いましょう。\nおわり Python弱者すぎてこんなので1日消耗してしまったので、備忘録として書いてみた。\nPyPIやpipについてあまり理解せずに使ってしまっていたので勉強になったが、\nそもそも古いバージョンに固定して放置している怠惰な自分が悪いわけで、\nおそらく大多数の人はひっかからない気がする。\n他のパッケージでもpip install時のログで急にtar.gzをダウンロードしてるときはPyPIでwheelが配布されているか確認するとよさそう。\n弱者はおとなしく新しいバージョンを使おう!\nおまけ ","date":"2025-10-15T00:00:00Z","image":"/post/2025-10-15-old-wheels-removed-from-pypi/sotochan.jpeg","permalink":"/post/2025-10-15-old-wheels-removed-from-pypi/","title":"PyPIで古いバージョンのwheelが削除されてないちゃった"},{"content":"そとちゃん 発作? 9月2日のお昼。\nいつも通り家で仕事をしていると、\nそとちゃんが居る居間から「ガタン」と大きな音とそとちゃんの鳴き声がした。\n様子を見に行くとバリバリベッドの横でそとちゃんが転がっていた。\n最初は寝ぼけて落ちたのかと思ったが、\nなにか様子がおかしい。\nよくみると黒目が左右に揺れている。\n歩き方もふらついていて、\n至る所にぶつかってしまう。\nおそらく発作のようなものが起きていて、\n明らかにいい状態ではないのですぐに病院へ。\nこの時点では発作の原因は不明。\n血液検査では腎臓の数値が少し高めなこと以外は何も見つからず、\n身体検査も脚の神経反射は正常で、骨折などの怪我もなさそうだった。\n突発性の発作は高齢猫に起こりうるものなので、\nまずは様子を見て発作(のようなもの)が1日に何回も起きたり、\n5分以上の長い発作が出るようなら専門的な治療を検討するということになった。\n若干脱水気味だったので、\nこの日は点滴だけ打って帰宅。\nふらふら生活 病院から戻った後はケロッとしていて、\nおやつを食べていつも通りゴロゴロ。\n基本的には今まで通り元気なのだが歩くときはふらふらしていて、\nジャンプや着地に失敗することが多かった。\nだんだん自分でも嫌になってきたようで、\n1週間くらい経った頃には自分から高いところに登らなくなり、\nずっとベッドでゴロゴロしていて元気もなくなってしまった。😭\n懸念の発作は突然体がビクッと動いて眼振が出るようなものがたまにあったが、\nそんなに長続きするものでもなく週に1,2回あるかないか、という感じだった。\nあまりひどい発作はなさそうだが見ていないところで危ない転び方をされると怖いので、\nこのタイミングでニトリのペットサークルを購入。\n本人は嫌そうだが、安全のためには仕方ない\u0026hellip;\n猫ドック 最初の受診から約2週後。\nあれからもひどい発作はないもののそとちゃんの調子があまり戻らないことと、\n前回(2月に実施)からの頻度がちょうどよかったのもあり、\n半年ぶりの猫ドックを受けることにした。\n朝に病院に預けて、夕方お迎え。\n詳細な結果は後日受領したのだが、\nお迎えのタイミングで当日わかった所見として 左耳中耳炎からの前庭疾患 という診断が出た。\n左耳がかなり汚れていたのと、\nレントゲンでも左側の耳の骨が溶けているような見え方をしていることから外耳炎または中耳炎はほぼ確定、\nさらに発作(眼振)やふらつきといった症状が継続しており前庭疾患の疑いが強いということだった。\n前庭疾患の特徴としては平衡感覚が狂って気持ち悪い状態が続くもの。\n実際に出ている症状もまさにそれに起因する感じなのでほぼ決まり。\nまた、眼振というのは異常がある方向に引っ張られることが多いらしく、\n眼振の動画をよくみると黒目が左耳方向に引っ張られてからゆっくり中央に戻るような感じで、\nまさに左耳が原因と思われる。\nまずは原因がわかって一安心。\n(実は初診の際、発作の原因が不明の場合は手の打ちようがない=発作を緩和する治療しかないと言われていた)\n他に腎機能の低下などもあったのだが、\nひとまず中耳炎の治療を最優先で進めることに。\n完治までは3ヶ月から半年くらいはかかるとのことで、\nそとちゃんの長い闘病生活が始まった。\nおくすり 中耳炎の治療では抗生剤を飲むことになり、\n今回はリレキシペットA錠75が処方された。\nまずは体の内側から炎症の原因となる菌を抑えるのが目的で、\nこれが効いてきたら今度は体の外側から与える薬に移行していくとのこと。\nお薬を飲むのは得意なそとちゃん。\nいつも通り砕いてちゅーるに混ぜたらちゃんと食べてくれた。\nこれが効くとよかったのだが\u0026hellip;\n通院① 当分の間は毎週通院することになったので、1週間後に再診。\n結果はなかなか厳しいものだった。\n1週前からの症状が改善していなかったのに加え、\nさらに耳が痛いのか首が傾くようになるなど悪化している様子だった。\n元々長期間の治療を想定していたが、\n抗生剤が菌に合ってない可能性を考えて薬を追加することになった。\n新しく追加したのはビクタスSS錠20mgで、\nこれまで与えていたお薬よりも対象となる菌の種類が多いため効果が期待できるとのこと。\nただしこれは耐性菌ができやすいということでもあるので、\n今後を考えると先生もあまり使いたくなかったらしい。\nそのため、この日は菌を特定するために耳垢、膿を採取して試験(培養)に回し、\n対象が特定できたらそれに効く薬に変えることになった。\nさらに脱水気味の状態だったので、点滴を打ってもらってこの日は帰宅。\n食欲不振 お薬を増やしてから最初の数日はちゃんと食べてくれていたのだが、\nだんだんと薬入りちゅーるへの反応が悪くなってきた。\n薬が増えて味が悪くなったのか前庭疾患による食欲不振なのかはわからないが、\nあんなに大好きだったちゅーるに反応を示さないというのはかなりショックだった。\n試しに薬を薄めたり薬なしのちゅーるも出してみたがそれすら無反応なので、\nおそらく後者(食欲不振)が原因だとは思うが\u0026hellip;\n他のおやつなら気分が向けば(?)たまに食べることもあるので、\nそれを祈るしかないという状況\u0026hellip;\n通院② さらに1週後、9月4度目の通院。\n状況はあまり良くなっていなかった。\n初期のような眼振は発生しなくなったが、\n依然ふらつき等の症状はそのまま。\nさらに前回の診察で試験に回した耳の菌はサンプルが弱く特定まで至らなかったため、\n再度試験を行いその結果が出るまでは今のお薬2種を継続することになった。\n特に心配だったのは体重がさらに減っていたこと。\n猫ドックの時でも3.34kgしかなかったのに、\nこの1週間で3.22kgまで落ちてしまっていた。\nこの週はご飯とおやつをほぼ残していたのでおそらくそれが原因。\n特に薬入りちゅーるを食べないと状況もよくなるはずがないので、\n今後はそとちゃんの口を開けて強制的に錠剤を飲ませることになった\u0026hellip;\nおわり そとちゃんの病気に気づけなかったのがただただ悲しい。\n先生からは適切な頻度で健康診断(猫ドック)を受けていたのが良かったとは言われたものの、\n普段から耳を掻いているところを見ていたのになんで気づけなかったのか？と自分を責める気持ちも出てきてしまう。\n9月は最初から最後までこんな感じでそとちゃんの状態が悪く、\nそれに引きずられる形で自分自身の精神も正直あまり良い状態ではなかった。\nもちろん一番辛いのはそとちゃんなので早く治してあげたいが、\n先は長そう\u0026hellip;\nおまけ ","date":"2025-10-11T00:00:00Z","image":"/post/2025-10-11-sotochan/sotochan.jpeg","permalink":"/post/2025-10-11-sotochan/","title":"9月のそとちゃん"},{"content":"そとちゃん バリバリベッド そとちゃんは窓際にあるバリバリベッドがすき。\n使いすぎてボロボロになってきたので、新しいものを購入。\n新品ってこんなに綺麗だったのか\u0026hellip;\n並べてみると一目瞭然。\n新しい方も気に入ってくれるか心配だったけど、\n割とすんなり使ってくれた。\n気に入ってくれたところで古いものは処分しようと思うのですが\u0026hellip;\nダメでした。\n意図せず増築という形になり部屋が狭くなってしまったが、\nねこは喜んでるのでOKです。😭\nよかったね〜😊\n鼻水ずるずる ある日ちょっと元気がないなと思っていたら、\n4月以来の鼻水が出てしまった。\n前回ほどひどいくしゃみや鼻くそはないんだけど、心配なので病院へ。\n診断結果は鼻炎。\n前回と同じ(?)点鼻薬を処方してもらった。\n診察のついでにワクチンも打ってもらった。\n去年の9月以来で、\nちょっと早めだけどそれくらいならOKらしい。\nすこし怪しいのは体重が結構落ちてたこと。\n4月の時点では3.8kgだったのが、\nこの日は3.46kgまで落ちてしまっていた。\nごはんを残しがちだったので多少は落ちていると思ったが、\nここまで急激に下がっているのはびっくりだった。\n先生とも相談して、\n食べないよりはマシということでおやつ(≠総合栄養食)でもいいからたくさん食べさせる方針になった。\nしょうがないね\u0026hellip;😭\nごはん 点鼻薬を使って数日で、\n鼻水がかなりおさまった。\nそのおかげかわからないが、\nあんまり食べなかったごはんをちょっと多めに食べてくれるようになった。\n総合栄養食はおやつに比べて匂いが弱めなので、\n鼻が詰まってる間はわかんなかったのかな\u0026hellip;？\nこの調子で体重も戻るといいな〜🙏\nおわり 体重がガタ落ちしているのは心配。\n鼻も治ったしおやつも増やしたので、\nたくさん食べて少しは戻るといいが\u0026hellip;\nこれを書いている9月はさらに不穏な兆候が出ており、\nあまりいい状態ではない、というのが正直なところ。\nはやく健康な状態に戻るといいな\u0026hellip;😭\nおまけ ","date":"2025-09-13T00:00:00Z","image":"/post/2025-09-13-sotochan/sotochan.jpeg","permalink":"/post/2025-09-13-sotochan/","title":"8月のそとちゃん"},{"content":"そとちゃん ごはん問題 最近またそとちゃんがごはんを残すようになってしまった😭\n去年のごはんイヤイヤ期はウェットフードを変えることでなんとか改善したが、\n今回は同じごはんでも日によって食べたり食べなかったり\u0026hellip;という感じで手強い。\n特定の味が嫌いになったとか飽きたならわかるんだけど、\n食べるときは食べるというのが厄介。\nおやつならいつでも食べるので、\n(毎度のことだが)やっぱり食欲不振ではなさそう。\nおやつトッピング作戦を試すが、\nそれも効果は薄めというか今度はトッピングだけ食べる悪質行為が発生してしまう😭\nそれでも食べないよりはマシだが\u0026hellip;\n困っちゃうねえ\u0026hellip;\nねこが落ちてる 常に冷房ガンガンの部屋なのに、\n夏になるとねこが落ちているのは何故だろう\u0026hellip;\n夏バテ？\n気がつくといろんなところで落ちていてかわいい。\n落ち方にもいろいろあって、見ていて飽きない。\nいいね〜〜👍\nスツール だいぶ前に買ったニトリのペットが入れるスツールが何故か今になってお気に入り。\nすごいときはトイレ以外丸一日ここで過ごしたりする。\n何がそんなにいいのかは謎だけど、\nたくさん使ってくれるのはうれしい。\n買ってよかった〜👍\nおわり ごはんは残すけど、ねこは元気。\n暑さに負けず(?)毎日自由に暮らしている。\n健康のためにもごはん、残さないでほしいが\u0026hellip;😭\nおまけ ","date":"2025-08-15T00:00:00Z","image":"/post/2025-08-15-sotochan/sotochan.jpeg","permalink":"/post/2025-08-15-sotochan/","title":"7月のそとちゃん"},{"content":"そとちゃん 袋 そとちゃんは袋が好き。\nニンゲンの遠出でお留守番してもらったときはお土産を色々買ってみるけど、\n一番喜ぶのはやっぱり紙袋。\nその中でも最近はスタジオツアー東京でもらった紙袋が結構いい感じ。\n紙が結構丈夫で、あんまりへたらない。\n激しく遊んでもまだまだ入れちゃうのが良い。\n硬ければ良いわけではなく、\nブランド物とかの加工された紙袋だと硬すぎてお気に召さないことが多いのだが、\nこれは程よい硬さがいい感じっぽい。\n気に入ってくれてよかった〜👍\nごっちゃし座布団 おいしいもつ煮が食べられると聞いてグルメ目的で幕張の野球場に行ってみたら、\n入場特典でごっちゃし座布団がもらえたのでそとちゃんに贈呈してみた。\n(野球の試合をやっていた気もするけどもつ煮が美味しすぎてあまり覚えていない)\n座布団はかなり小さめで、ねこにはちょうどいい大きさ。\nしかもかなり軽くて、\nあんまり強くないそとちゃんの力でも爪を引っ掛ければ動かせるのでいい相撲相手になっている。\nボロボロになるまで遊ぼうね\u0026hellip;\nお風呂 ブラッシングが嫌いでサボりまくりのそとちゃん、\nある日毛玉をちょっと多めに吐いてしまった。\n普段は上手に吐くのだが、\n今回はそこそこ汚れてしまったので久しぶりのお風呂が決定。\n最後に入ったのは2~3年前?\nだいぶ久しぶりだった。\n例に漏れずそとちゃんも水が苦手なので、なるべく手短に済ませる。\nまずは湯船に張ったぬるま湯で身体を濡らす。\n地肌まで濡れたらねこ用シャンプーで全身を洗い、\nやさしめのシャワーともう一度湯船ですすいで終わり。\nそとちゃんは協力的ではないが抵抗が弱めだったので、\nお風呂自体は30分かからないくらいで終了。\n後はタオルドライと、\n暴れるのでちゅーるで釣りながらぬるめのドライヤーで仕上げ。\nおつかれさまでした\u0026hellip;🛁\nおわり やはり夏はいろいろめんどくさくて更新が遅れてしまう。\n7月中旬にこれを書いているのだが今年の夏も暑いので、\n冷房をうまく使ってねこも人間も体調を崩さないように気をつけたい。\nおまけ ","date":"2025-07-21T00:00:00Z","image":"/post/2025-07-21-sotochan/sotochan.jpeg","permalink":"/post/2025-07-21-sotochan/","title":"6月のそとちゃん"},{"content":"そとちゃん 誕生日 5月1日はそとちゃんの誕生日。🎂\n元野良なので推定だけど、\nうちに来たのが5月1日なのでそういうことにしている。\n今年で推定9さい。\n3さいのときにうちに来てもう6年\u0026hellip;\n推定年齢が間違っていなければ野良だった期間の倍以上はうちで一緒に過ごしていることになる。\nすこしは家猫っぽくなっただろうか？\nとりあえず元気に誕生日を迎えられてよかった。\n(実はこのときまだちょっと鼻水出てたけど\u0026hellip;)\n今年の誕生日プレゼントは色々考えたがこれというものが決まらず、\nくったくたになってしまった寝床のしろたんを修理してあげることにした。\nうちにきたときからずっと寝床にしているしろたん。\n同じポジションでしか使わないもんだから、\nもう底に触れるくらい綿が抜けてしまって寝心地が悪そう。\n中身の綿袋を外し、\nユザワヤで綿とテープを買ってきて補修\u0026hellip;\n素人作業だけどなんとか復活できたのでこちらをプレゼントとして献上。\n\u0026hellip;が。\nどうやら綿をパンパンに詰めすぎてしまったようで、\nクッション性がなくなってしまった。\nそとちゃんが乗っても全然沈まなくて、寝返りを打つと転げ落ちてしまう。\nやってしまった\u0026hellip;\n結局そとちゃんもお気に召さず、\n今はモチモチクッションを寝床にしている。\nプレゼントはコケてしまったので、\nごちそうで挽回。\nこれでなんとかゆるしてほしい。\nしろたんはいっぱいこねてまた自分好みにできるといいですね\u0026hellip;\n記念写真 5月中旬には鼻水もだいぶ治ったので、\n毎年恒例の記念写真を撮りに行った。\n相変わらずスタジオではやりたい放題。\nスタッフさんも慣れっこなので、\n一通り遊ばせてもらいそとちゃんが落ち着いてから撮影開始。\nたくさん遊んで満足したのか、\n今年はおとなしくねこ単体でかなりの枚数を撮影することができた。\nがんばりました\u0026hellip;\nおわり 無事にそとちゃんが9さいの誕生日を迎えられた。🎉\n歯は数本抜けちゃったし、ちょこちょこ病院に行く回数も増えてきたけどそとちゃん本人(猫)はまだまだ元気いっぱい。\nねこの9さいは人間でいうと50~60歳くらいのレディなのでそれなりに敬って接したほうが良いのかもしれないが、\nそとちゃんは小さくてかわいいし自分からすると今も赤ちゃんみたいでベロベロに甘やかしている。\n10歳の誕生日も元気に迎えられるよう頑張りましょうね\u0026hellip;(主に人間が)\nおまけ ","date":"2025-06-06T00:00:00Z","image":"/post/2025-06-06-sotochan/sotochan.jpg","permalink":"/post/2025-06-06-sotochan/","title":"5月のそとちゃん"},{"content":"そとちゃん こたつ 4月になり暖かい日も増えてきたが、\nそとちゃんは相変わらずこたつを堪能している。\n最近はあまり電源を点けないので中に入ることは少なくなってきたが、\n代わりにこたつに入った人の膝に乗るのがお気に入りになっている。\nこのポジションはかなり好きみたいで、\nこたつに入るとすぐに乗ってくるし誰もこたつに入っていないと鳴いて呼びにくる。\nかわいいね\u0026hellip;\n(片付けられない)\n箱 最近また荷物が届いたので箱を献上。\n元々袋派のそとちゃんも、\n今ではすっかり箱を堪能するようになっている。\n(単に袋が最近溜まらないだけなのだが)\n今回の箱はフィット感が良く(?)、\nよく入って遊んでいる。\n楽しそうでなにより。\n鼻水 月の後半からそとちゃんの鼻水が目立つようになってしまった。\n慢性的にくしゃみが出ていて、\nずっと鼻をふがふがさせている。\nそれ以外は食欲もあり元気なのだが、\n1週間くらい経って透明だった鼻水が若干白くなり、\n鼻くそも目立つようになったので念のため病院へ。\n当日の体重は3.8kgで痩せ気味だがあまり問題なし、\n体温も37.9℃でおおむね平熱だった。\n鼻水を採取してそのまま検査へ。\n結果は細菌感染による鼻炎という診断だった。\nワクチンを打っていたので風邪系はあまり無いと勝手に思っていたが、\n鼻水の成分的を見る限りは細菌に反応しているとのこと。\nなんの菌に感染しているのか特定するには時間がかかって大変なので、\nひとまずの対応として菌の繁殖を抑えるために抗菌性の点鼻薬(ベストロン)を処方してもらった。\n(これでも改善しないならより精密な検査、場合によっては菌の特定をするとのこと)\n猫の点鼻薬は初めてなのだが、\n目薬と同じ要領で上を向かせて後ろから垂らせばいいらしい。\n(目薬は昔何回か使ったことがある)\n家に帰って早速試したところ、\nなんとか鼻に入れることができた。\n(かなり嫌がるが\u0026hellip;)\nはやく治るといいな\u0026hellip;\nおわり 今年に入ってからちょこちょこ病院に行くことが増えてきたが、\n本人(猫)は割と元気なのが救い。\n5月はそとちゃんの誕生日(推定)もあるので、\n健康な9歳を迎えたいね\u0026hellip;\nおまけ ","date":"2025-05-05T00:00:00Z","image":"/post/2025-05-05-sotochan/sotochan.jpeg","permalink":"/post/2025-05-05-sotochan/","title":"4月のそとちゃん"},{"content":"そとちゃん スケーリング 歯肉炎がひどくて歯が抜けてしまったそとちゃん。\n残った歯を守るためにも2年ぶりのスケーリングを受けることに。\n当日の朝は珍しくキャリーに入るのを渋ったが無事病院へ\u0026hellip;\nスケーリングは全身麻酔が必要だったりでほぼ半日かかるので、\n朝病院に預けて夕方お迎えに行く形。\nあとは先生に任せてしばしの別れ。\n昼すぎには無事に終わった旨の電話をもらい、\n予定通り夕方再び病院へ。\n病院に着くと麻酔から目が覚めたそとちゃんがわんわん鳴いていた。\n今回も麻酔後にちょっと漏れたおしっこがお尻について機嫌が悪かった様子。\n肝心のスケーリングだが、\n先生が術前術後の写真を撮っており、見せてもらうことができた。\nなんか光の当て方じゃね？と思ったけどちゃんと歯石は取れているらしい。\nあと気持ちの問題な気もするが口臭もちょっとマシになった気がする。\n麻酔のために前日の夜から約20時間近くごはんをあげてなかったので、\n帰宅後はごほうびごはんをドカ食い。\nがんばりました\u0026hellip;\nお水 ねこの水飲み場は多いほうがいいらしい。\n知らなかったわけではないのだが、\nそもそも1箇所にしかなくてもそとちゃんはかなり水を飲んでくれるのであまり気にしていなかった。\nある日なんとなく、\n本当になんとなく余っていた水飲み皿に水を入れて部屋に置いてみたのだが、\nそとちゃんがいきなり食いついた。\nそれはそれでいいんだけど、\n逆にこれまで使っていたピュアクリスタルの水を全然飲まなくなってしまった\u0026hellip;\n今までのはポンプと猫用のフィルタで濾過、\nしかもヒーターでねこ好みに温めてるめちゃんこお金のかかった水なのに、\nそれよりも皿に汲んだだけの水道水を好むとは\u0026hellip;?🤔\n水はここ、と決めたら変えたくない的なこだわりがあるのかな？\nよくわからないがたくさん飲んでくれるならヨシ！😭💸\n洗濯機 家で使ってた洗濯機がもう10年以上で耐用年数を超えていたので、\nいい機会だと思いニトリの激安ドラム式洗濯機を買ってみた。\n(めっちゃ異音がする初期不良を引いて色々あったのだがそれは割愛、安いけどちゃんとサポートしてくれたから不満なし)\n洗濯機といえば、うちには洗濯機大好きねこちゃんが一匹。\n設置早々に洗濯機を調べるそとちゃん。\n今までは縦ドラムだったから頑張ってジャンプしないと入れなかったのだが、\n横ドラムになったので簡単に入れてしまう。\n(⚠️そもそもねこは洗濯機に入っちゃダメです)\n好きすぎて中に洗濯物があっても無理やり突破しようとする。\nドラムの中だけでなく、 洗濯中も気を抜くとたまに上にのぼってしまう。\n今回買った洗濯機は安いのでヒーター式乾燥なのだが、\n乾燥中は機械がかなり暖かくなるのでそれが気に入ってしまった様子。\nドラムの回転でガンガン揺れててもお構いなし。\n洗濯機の上でねこがぷるぷる震えているのはちょっと面白いがやっぱり危険なのでダメです。\nおわり 2月はそとちゃんがちょっと大変だったけど、\n3月はほとんど元気に楽しく過ごせてよかった。\nお水の件も洗濯機の件もそうだけど、\nそとちゃんはなんでもすぐ好きになってくれるので一緒にいて楽しい。\n怖がるという賢さはあんまりないんだろうな\u0026hellip;😂\nあと歯磨きを頑張ってこの状態をキープしましょう😤\nおまけ ","date":"2025-04-12T00:00:00Z","image":"/post/2025-04-12-sotochan/sotochan.jpeg","permalink":"/post/2025-04-12-sotochan/","title":"3月のそとちゃん"},{"content":"まとめ Azure SDK for Pythonを使っていてサービスへの接続時に証明書検証エラーSSL: CERTIFICATE_VERIFY_FAILEDが出た時、\n雑に回避したかったら各クライアントのコンストラクタにconnection_verify=Falseを渡せば良い。\n(本番運用するような場合はちゃんとルート証明書を端末のRootCAに追加しよう！怒られても知らないぞ！)\n# こんな感じ blob_service_client = BlobServiceClient.from_connection_string(connect_str, connection_verify=False) 経緯 会社のうんこPC ちょっと特殊な環境で開発しているときにAzure SDK for PythonからAzureのサービスを呼び出したところ証明書エラーが出てしまった。\nazure.core.exceptions.ServiceRequestError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1028) このエラーは実行環境が信頼しているCA証明書でサーバー(Azure)側の証明書検証がしてしまうというもので、\n正攻法としては信頼するCA証明書のリストにAzureのルート証明書を追加すれば解決するのだが、\nPCがうんこすぎてそこらへん全然自由にいじれない\n割と急いでたり動作検証で一回だけ通ればよかったりするときにわざわざ証明書をいじるのはけっこうめんどくさい。\n全然汚い方法でいいからサクッと解決したくなった(=証明書検証自体をスキップしたい)。\n環境 Python 3.13.2 azure-storage-blob 12.25.1 Azure Blob Storageは作成済みとする 手順 キーワード引数を指定 とりあえず使うコードはこんな感じ。\n対象とするAzureサービスはなんでもいいが今回はBlob Storageのクライアントを使う。\nこれを特定の環境で実行したとき、次のようなエラーが出るものとする。\n(接続文字列はportalから取得できる)\n# 依存関係入れる $ pip install -r requirements.txt # ... # 接続文字列を環境変数に設定 $ export AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...;EndpointSuffix=core.windows.net # スクリプト実行、証明書検証エラー発生 $ python main.py Traceback (most recent call last): File \u0026#34;/test/main.py\u0026#34;, line 14, in \u0026lt;module\u0026gt; for container in container_list: ^^^^^^^^^^^^^^ File \u0026#34;/usr/local/lib/python3.13/site-packages/azure/core/paging.py\u0026#34;, line 123, in __next__ return next(self._page_iterator) File \u0026#34;/usr/local/lib/python3.13/site-packages/azure/core/paging.py\u0026#34;, line 75, in __next__ self._response = self._get_next(self.continuation_token) ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ File \u0026#34;/usr/local/lib/python3.13/site-packages/azure/storage/blob/_models.py\u0026#34;, line 538, in _get_next_cb return self._command( ~~~~~~~~~~~~~^ marker=continuation_token or None, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ maxresults=self.results_per_page, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cls=return_context_and_deserialized, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ use_location=self.location_mode) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File \u0026#34;/usr/local/lib/python3.13/site-packages/azure/core/tracing/decorator.py\u0026#34;, line 105, in wrapper_use_tracer return func(*args, **kwargs) File \u0026#34;/usr/local/lib/python3.13/site-packages/azure/storage/blob/_generated/operations/_service_operations.py\u0026#34;, line 685, in list_containers_segment pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ _request, stream=_stream, **kwargs ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ) ^ File \u0026#34;/usr/local/lib/python3.13/site-packages/azure/core/pipeline/_base.py\u0026#34;, line 240, in run return first_node.send(pipeline_request) ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^ File \u0026#34;/usr/local/lib/python3.13/site-packages/azure/core/pipeline/_base.py\u0026#34;, line 96, in send response = self.next.send(request) File \u0026#34;/usr/local/lib/python3.13/site-packages/azure/core/pipeline/_base.py\u0026#34;, line 96, in send response = self.next.send(request) File \u0026#34;/usr/local/lib/python3.13/site-packages/azure/core/pipeline/_base.py\u0026#34;, line 96, in send response = self.next.send(request) [Previous line repeated 2 more times] File \u0026#34;/usr/local/lib/python3.13/site-packages/azure/core/pipeline/policies/_redirect.py\u0026#34;, line 204, in send response = self.next.send(request) File \u0026#34;/usr/local/lib/python3.13/site-packages/azure/core/pipeline/_base.py\u0026#34;, line 96, in send response = self.next.send(request) File \u0026#34;/usr/local/lib/python3.13/site-packages/azure/storage/blob/_shared/policies.py\u0026#34;, line 555, in send raise err File \u0026#34;/usr/local/lib/python3.13/site-packages/azure/storage/blob/_shared/policies.py\u0026#34;, line 527, in send response = self.next.send(request) File \u0026#34;/usr/local/lib/python3.13/site-packages/azure/core/pipeline/_base.py\u0026#34;, line 96, in send response = self.next.send(request) File \u0026#34;/usr/local/lib/python3.13/site-packages/azure/core/pipeline/_base.py\u0026#34;, line 96, in send response = self.next.send(request) File \u0026#34;/usr/local/lib/python3.13/site-packages/azure/core/pipeline/_base.py\u0026#34;, line 96, in send response = self.next.send(request) [Previous line repeated 1 more time] File \u0026#34;/usr/local/lib/python3.13/site-packages/azure/storage/blob/_shared/policies.py\u0026#34;, line 301, in send response = self.next.send(request) File \u0026#34;/usr/local/lib/python3.13/site-packages/azure/core/pipeline/_base.py\u0026#34;, line 96, in send response = self.next.send(request) File \u0026#34;/usr/local/lib/python3.13/site-packages/azure/core/pipeline/_base.py\u0026#34;, line 96, in send response = self.next.send(request) File \u0026#34;/usr/local/lib/python3.13/site-packages/azure/core/pipeline/_base.py\u0026#34;, line 128, in send self._sender.send(request.http_request, **request.context.options), ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File \u0026#34;/usr/local/lib/python3.13/site-packages/azure/core/pipeline/transport/_requests_basic.py\u0026#34;, line 409, in send raise error azure.core.exceptions.ServiceRequestError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1028) ここで証明書検証をスキップするようにしたいが、\n使ってるクライアントにそれっぽいオプションがなさそう\u0026hellip;詰んだ\u0026hellip;\nと思ったが、ちょっとエラーを調べるとこのクライアントが内部で呼び出してるクラスとそのパラメータみたいなのが出てきた。\nhttps://github.com/Azure/azure-sdk-for-python/blob/21ba181f927de0a6580ad75a068dd9c5045420e9/sdk/storage/azure-storage-blob/azure/storage/blob/_blob_service_client.py#L54 https://github.com/Azure/azure-sdk-for-python/blob/21ba181f927de0a6580ad75a068dd9c5045420e9/sdk/core/azure-core/azure/core/pipeline/transport/_requests_basic.py#L259 https://learn.microsoft.com/ja-jp/python/api/azure-core/azure.core.configuration.connectionconfiguration?view=azure-python (BlobServiceClientがHTTPリクエストを送るメソッドの内部でConnectionConfigurationを呼び出していて、このclassにはconnection_verifyといういかにも証明書検証の有無を司ってそうなパラメータがある)\n試しにキーワード引数(kwargs)をクライアントのコンストラクタに渡してみよう\u0026hellip;\nコードを書き換えて再度実行したところ、\n「ちゃんと証明書検証したほうがいいぞ」的な警告は出るものの今度はエラーにならずちゃんと呼び出しが成功した(コンテナ名の一覧が表示できた)。\n$ python main.py /usr/local/lib/python3.13/site-packages/urllib3/connectionpool.py:1097: InsecureRequestWarning: Unverified HTTPS request is being made to host \u0026#39;stuzimihsrexample.blob.core.windows.net\u0026#39;. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings warnings.warn( example-container-1 example-container-2 azure.core.configuration.ConnectionConfigurationは他のAzureサービスのクライアントでも通信時に使われているようなので、\n同様に各クライアントのコンストラクタ引数にconnection_verify=Falseを渡せば証明書検証を回避できるはず。\nやったぜ。\n(証明書エラーの再現手順) 普通の環境で実行しても証明書エラーに遭うことはなかなかないので、\n今回は問題を再現するため以下の手順で信頼するCA証明書が欠けているような状態を作った。\n雑にDockerでPythonコンテナを立ち上げ、RootCAのリストを取得する。\n$ # 作業ディレクトリ $ pwd /path/to/tempdir $ ls -1 test main.py requirements.txt $ # testディレクトリをマウントしてPythonコンテナを立て、そのままbashで入る $ docker container run --rm -it -v /path/to/tempdir/test:/test python:3 /bin/bash root@f8a544bd9c26:/# root@f8a544bd9c26:/# # 依存関係入れる root@f8a544bd9c26:/# cd test root@f8a544bd9c26:/test# pip install -r requirements.txt # ... root@f8a544bd9c26:/test# # RootCAの場所を確認 root@f8a544bd9c26:/test# python Python 3.13.2 (main, Mar 18 2025, 22:43:33) [GCC 12.2.0] on linux Type \u0026#34;help\u0026#34;, \u0026#34;copyright\u0026#34;, \u0026#34;credits\u0026#34; or \u0026#34;license\u0026#34; for more information. \u0026gt;\u0026gt;\u0026gt; import certifi \u0026gt;\u0026gt;\u0026gt; certifi.where() \u0026#39;/usr/local/lib/python3.13/site-packages/certifi/cacert.pem\u0026#39; \u0026gt;\u0026gt;\u0026gt; exit root@f8a544bd9c26:/test# # RootCAをコピーしてホストOS側で編集する root@f8a544bd9c26:/test# cp /usr/local/lib/python3.13/site-packages/certifi/cacert.pem ./cacert.pem 拾ってきたRootCAをホストOS側から任意のエディタで開き、\n# Issuer: CN=DigiCert Global Root G2の部分のpem(画像では1740~1768行目)をごっそり削除する。\n(Blob StorageのRootCAがDigiCert Global Root G2であることはブラウザで確認した。)\nこれで信頼されたRootCAのリストからBlob StorageのRootCAが消えたので、\nこれをPythonが参照するように上書きする。\nroot@f8a544bd9c26:/test# # 編集後のcacert.pemで上書き root@f8a544bd9c26:/test# cp ./cacert.pem /usr/local/lib/python3.13/site-packages/certifi/cacert.pem あとはAzure portalから接続文字列をコピーしてきて環境変数に張り付けて、\nroot@f8a544bd9c26:/test# # 接続文字列を環境変数に設定 root@f8a544bd9c26:/test# export AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...;EndpointSuffix=core.windows.net コードを実行したらちゃんとエラーになった。\nroot@f8a544bd9c26:/test# python main.py Traceback (most recent call last): File \u0026#34;/test/main.py\u0026#34;, line 14, in \u0026lt;module\u0026gt; for container in container_list: ^^^^^^^^^^^^^^ #... azure.core.exceptions.ServiceRequestError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1028) おわり うんこPCで開発すんのマジしんどい、営業マンと同じ構成のポンコツ制限PCをエンジニアに使わせるんじゃないよ 超久しぶりにPython触ったら色々わかんなすぎて泣いてしまった。\nやっぱりGoがいいなあ\u0026hellip;\nおまけ ","date":"2025-03-29T00:00:00Z","image":"/post/2025-03-29-azure-sdk-for-python-ignore-cert-verify/sotochan.jpeg","permalink":"/post/2025-03-29-azure-sdk-for-python-ignore-cert-verify/","title":"Azure SDK for PythonでCERTIFICATE_VERIFY_FAILEDを雑に回避する"},{"content":"そとちゃん 歯が抜けた ある日の夜。\n寝る前に日課の歯みがきシートをしていたら、\nそとちゃんが嫌がって動いたときに歯が引っかかってそのままポロッと抜けてしまった。😱\n血が出たものの、ねこ自身は全く気にしてない様子。\n念の為病院に電話したところ、\n血が止まって本人(ねこ)が痛がるそぶりもなければ急診の必要はないとのことだった。\n抜けたのは右上の奥歯。\n元々右側の歯は歯肉炎がひどくて、\n2年前にスケーリング(歯石除去)したときに一番奥の歯を抜歯していたのだが、\n今回は手前の歯が抜けてしまった\u0026hellip;\n翌朝さっそく病院へ。\n診察の結果、\nとくに異常はなし。\n気になるなら抗生剤を出してもよいが、\n血が止まっているなら必須ではないとのことなので一旦なしにしてもらった。\nあとは残った右下の歯も歯肉炎がひどかったので、\n後日再びスケーリングをすることに。\n歯磨きは毎晩やっていたが、\n左側に比べて右側は痛がるのであまりうまくできていなかった。\n反省\u0026hellip;\n猫ドック スケーリングは全身麻酔が伴うため、\n安全のために血液検査が必要。\nどうせ血液検査をやるならということで、\nそとちゃんは初めての猫ドックを受けることになった。\n朝に病院に預けて、 夕方までみっちり検査。\n病院大好きそとちゃんも流石に疲れたのか、\n家に帰ってきたころにはすっかりしょんぼりしていた。\n加えて前日夜からご飯を抜いていたので、\nめちゃめちゃお腹が空いていた様子。\n一応検査後に病院でもおやつをもらったはずだが、\n帰宅後すぐにごはんをドカ食い。\nがんばりました\u0026hellip;\n気になる結果は\u0026hellip;？\n身体検査\n体重は3.9kgでちょっと痩せ気味。\n体格的に4.0kgは欲しいがごはんを好き嫌いしていたのもあって減ってしまった。\n体温は平熱(38.2℃)で問題なし。\n右眼は相変わらず目ヤニが多め。\nタンパク質多めの食事にすると改善するかも？\n目薬を処方することもできるが、\nほぼ生まれつきのようなもの(猫風邪の後遺症)なのでこまめに拭くのが一番らしい。\n口は主に右側の歯肉炎が引っかかった。\n歯列異常の診断は歯が抜けてしまっているからとのこと。\n皮膚はフケが目立ち、\n左耳の付け根に脱毛があった様子。\nフケは一時的なストレス、\n脱毛は脚で掻く癖があるのでそれ由来っぽい。\n掻きすぎて出血などがあれば治療しなきゃだけど、 今の所問題なし。\n夏になったら久しぶりにお風呂入ろうか\u0026hellip;\n触診は背中を押した時に嫌がるそぶりを見せたので気をつけたほうがいいらしい。\nとはいえレントゲン(後述)では異常はなかった。\nその他は異常なし。\nいろいろ引っかかってしまったが、\n緊急で対応が必要というよりは「今後も注意して見てね」くらいのものだったので一安心。\n便検査 尿検査\nうんちは異常なし。\n形も成分もいい感じ。\nおしっこは尿比重が低いのが問題とのこと。\n薄すぎるので腎機能が低下している可能性がある。\nそとちゃんは元々水を良く飲むのでそれが関係しているかもしれないが、\n腎機能が落ちた猫は水をたくさん飲む傾向にあるのでやはり注意が必要。\n1回のおしっこの量が異常に増えたりしたら再検査も視野に入れたほうが良いらしい。\nレントゲン検査\nおおむね問題なしだが、\n気管支パターンが見つかったので咳が増えたら注意が必要とのこと。\nよく水を飲んだ後にくしゃみか咳のような動きをよくやるので気をつけてあげたい。\nエコー検査\nこちらもおおむね問題なし。\n膵臓がわずかに肥大していたとのことだが、\n異常があれば食欲低下や嘔吐の症状が出るはずなので元々ちょっと大きいだけかもしれない。\n(そとちゃんは好き嫌いするだけで食欲が落ちたことはほぼない)\n血液検査\n大きな異常はなし。\n腎機能の値であるクレアチニンがわずかに基準値を上回っているが、\n元々ブレが大きい値であること、\n腎臓に本当に異常があるときすぐ上がる値(SDMA)が低いのでそこまで深刻な状態ではないらしい。\nMCHCと血小板数も少なかったが、\nこちらも基準値をギリギリ下回る程度なので大丈夫。\nこんな感じで大きな異常はなし、\n全身麻酔も打てるでしょうとのことで3月にスケーリングを行うことになった。\nおしっこ そとちゃんの病院通いはこれで終わらず\u0026hellip;😭\n猫ドックから帰ってから数日後、\nこんどはそとちゃんがほぼ初めての粗相をしてしまった。\nこれまでにそとちゃんが粗相をしたのはたったの一回、\nそれもうちにお迎えした初日でトイレの場所がわからなかったときだけだったので、\nとても心配になった。\n様子を見ていると何度もトイレに行ってはいるのだが、\nなかなかうまくおしっこが出ない様子。\n(うんちは問題なくできていた)\nトイレだとうまくおしっこできないから、\n他におしりの居心地がいい場所を探してそこでおしっこを試みる、\nといった動きをしているように見えた。\n移動した先でもなかなかおしっこが上手に出せず、\n血尿と思われるものがポタポタ出ている状態。\n辛そうなのですぐに病院へ。\nやはりおしっこがうまく出せず溜まっていたようで、\nすぐにカテーテルで全て排出してもらった。\n腎臓に問題があると排尿を我慢することもあるらしいが、\n直近の検査で問題ないことと、\n上記の症状や排出した尿から細菌の値が少し出たので細菌性膀胱炎との診断。\n原因としてはトイレでうんちや汚れた砂が尿道に触れたのかも？とのこと。\n素人的には猫ドックで採尿(膀胱穿刺)してもらったときに膀胱に傷がついたのでは？とも思ったが、\n実際は針ではなくカテーテルで採尿していたので考えづらいということだった。\n治療のため、\n1週間ほど抗生物質のアモキクリアを飲むことになった。\n帰宅後は何事もなかったかのようにケロッと復活。\n溜まってたおしっこがなくなってスッキリしたのかな？\nそして翌朝。\nなんとまだ2回しか薬を飲んでないのに普通にトイレでおしっこできるようになった。\n薬が効くにしてはちょっと早いと思うけどまあいいのかな？\n薬もしっかり飲みきり、\n以降は血尿も粗相もなくなった。\nトイレはこまめに砂を入れ替えてるけど、再発しないといいな\u0026hellip;\nおわり 2月はなんと3回も(1回は検査だけど)病院のお世話になってしまった。\nお財布に大ダメージなのはもちろんだが、\nやはりシニアねこ(次の5月で推定9さい)なので心配が大きい。\n歯が抜けたのも、毎日の歯磨きをもっと頑張っていれば防げたと思うと悔しい。\nこれで右上の歯は犬歯以外の臼歯が全部抜けてしまった。\n綺麗な左側の歯は全部守っていきたいな\u0026hellip;\nおまけ ","date":"2025-03-16T00:00:00Z","image":"/post/2025-03-16-sotochan/sotochan.jpeg","permalink":"/post/2025-03-16-sotochan/","title":"2月のそとちゃん"},{"content":"そとちゃん ホットカーペット 新年早々そとちゃんにうれしいお年玉。\n我が家にホットカーペットが導入された。🎉\n友人から譲り受けたもので(物々交換なのに対価をまだ渡せておらず申し訳ない)、\nうちの部屋にぴったりサイズ。\nすぐにそとちゃんのお気に入りになった。\n電源を入れるとすぐ乗ってきて溶けちゃう。\nかわいいね。\nたまに電源を落とすと明らかに機嫌が悪くなる。\n上手につかってくれるといいな\u0026hellip;\nもちもちクッション 良い子にしているそとちゃんに俺からもお年玉を\u0026hellip;\nニトリでもちもちクッションを購入。　直径60cmで、そとちゃんにちょうど良いサイズ。\nこちらも早速気に入ってくれて、\n気づけば上で丸くなっている。\n丸くないときもある。\n他のねこちゃんはこういったクッションでふみふみする子が多いみたいだけど、\nそとちゃんはやっぱりふみふみしなかった😂\n(自分がお母さん猫だから子猫しぐさはあまりしないのかな\u0026hellip;?)\nホットカーペットと合わせて楽しくすごしてくれたらうれしい。\nおわり 1月はそとちゃんに嬉しいプレゼントが多くてよかった。\nなんでも喜んでくれるからあげた方もうれしい。\nこれで冬を乗り切りたいな\u0026hellip;\n(2月現在、ホットカーペットで電気代が爆発中💸)\nおまけ ","date":"2025-02-28T00:00:00Z","image":"/post/2025-02-28-sotochan/sotochan.jpeg","permalink":"/post/2025-02-28-sotochan/","title":"1月のそとちゃん"},{"content":"そとちゃん 来客 12月はちょこちょこ来客があり、\n人間大好きなそとちゃんは大喜びだった。\nそとちゃんはマジでお客さんが大好きなので、\n自分からひざに乗りたがる。\n普段から全然面識のない配送員さんや業者さんにも絡んでしまうし、\n載せてもらえないと不機嫌になるくらいにはお客さんが好き。\n猫カフェでアルバイトできるんじゃないかしら\u0026hellip;😂\n麻雀 たびたび来客がある中で、\nなんとそとちゃんが麻雀好きなことが判明。🀄️\nマットの上に登って大胆に観戦。\nさらに牌もおててで上手に転がす。\nすごいなあ\u0026hellip;(ゲームにならない)\nクリスマス そとちゃんは今年もサンタさんになった。\nべらぼうにかわいいね\u0026hellip;\n今年も1年良い子にしていたそとちゃんにはプレゼントをご用意。\n楽しいクリスマスになってよかった。\nおわり 2024年もそとちゃんはよく食べて寝て遊んで元気だった。\n大きな環境の変化もあったが、\nそとちゃんはすぐに慣れてごきげんになってくれるので本当に助かる。\nしもべとしてはあまり良いことがない1年だったので、\n2025年はねこを見習って明るく楽しい年にしたい。\nおまけ ","date":"2025-01-12T00:00:00Z","image":"/post/2025-01-12-sotochan/sotochan.jpeg","permalink":"/post/2025-01-12-sotochan/","title":"12月のそとちゃん(2024)"},{"content":"そとちゃん こたつ だいぶ寒くなってきたのでこたつを導入。\n例年通りそとちゃんはこたつ大好きねこちゃんなので楽しそう。\nでもやっぱり人間が入ると嫌みたいで出ていってしまう。\nちなみにこたつを出た後は人間のひざに収まる。\n人間の脚が入っててもひざ上よりはこたつのほうが広くて快適だと思うのだが、\nねこ基準だとそうではない様子。\n仲良く共存できたらいいな\u0026hellip;\nふとん こたつも出したし、\n人間のふとんも暖かいやつに変更\u0026hellip;\nしたら今回もそとちゃんに気に入られてしまった。\nねこにはこたつがあるのに\u0026hellip;\n人間が寝ててもおかまいなし。\n躊躇なく上に乗って寝てしまう。\n(ちなみに布団をかぶるのは苦手なので中には入らない)\nまあ幸せそうなのでOKです。\nおわり ブログを更新しないままついに年を越してしまった。\n今冬はまだまだ寒さが続くがそとちゃんは元気いっぱい。\nこたつとふとんをうまく使って寒さを乗り切って欲しい。\nおまけ ","date":"2025-01-11T00:00:00Z","image":"/post/2025-01-11-sotochan/sotochan.jpeg","permalink":"/post/2025-01-11-sotochan/","title":"11月のそとちゃん(2024)"},{"content":"そとちゃん ごはん食べくらべ いつものごはんを食べなくなってしまったそとちゃん。\n何日も食べてくれないと流石に困るので、\n他のごはんを試してみることにした。\n(久しぶりに動画作るのが結構大変で時間かかってしまった\u0026hellip;)\n試したごはんと結果はこんな感じ。\nごはん 結果 MiawMiaw 🙂 黒缶に比べればまあ食べたな、という程度 CIAO 🙂 MiawMiawと同じくらい食べたけど少し食べづらそうだった WILD RECIPE 🤩 飲むように食べた MonPetit 🤩 よく食べた NaturaHa 😕 あんまり食べなかった 黒缶 😭 笑っちゃうくらい食べない ROYAL CANIN 😊 そこそこ食べた ROYAL CANIN(7+) 😊 シニア向けだけど、普通のと同じくらい食べた kalkan 🥰 めちゃめちゃ食べた 食べた順だとワイルドレシピとモンプチがかなりよかったんだけど、\nこの2つは1袋あたりの量が他のフードの半分くらいなので若干使いづらいかな\u0026hellip;?と思った。\n毎回2袋開けるのは手間もゴミも増えるし、\nなにより値段も高いし\u0026hellip;💸\nその次くらいによく食べたのがカルカン。\nお手頃価格なのに食いつきがよかった。\n庶民派ねこちゃんですね\u0026hellip;\nあとは自分のエゴでロイヤルカナンを選出。\nそろそろシニア向けフードにも挑戦しないとな\u0026hellip;と前から思っていたが、\n今回試した感じだと結構食べてくれそうなので。\nそんな感じで、\n当分の間はカルカンとロイヤルカナンを併用することにした。\nいっぱい食べてくれ〜🙏\nハロウィン 毎年のやつ。\n今年もあまり嫌がらず写真を撮らせてくれた。\nかわいいねえ\u0026hellip;\nおわり とりあえずご飯をちゃんと食べてくれるようになってよかった。\nこれを書いている時点ですでに12月なのだが、今年の残りも健康に暮らせるようにしたい。\nおまけ ","date":"2024-12-09T00:00:00Z","image":"/post/2024-12-09-sotochan/sotochan.jpeg","permalink":"/post/2024-12-09-sotochan/","title":"10月のそとちゃん(2024)"},{"content":"まとめ どうしてもDatabricksのワークスペースAPIをAPI Management経由で叩きたかったのだが、\n設定が結構大変な上になぞりやすい公式のドキュメントが見つからなかったので\n自分が設定できたときの手順を残しておく。\nDatabricksワークスペースAPIをAPI Managementのバックエンドとして利用する際はカスタムURLが使える API Managementに持たせる資格情報はDatabricksワークスペースのPATも利用可能だが、\nよりセキュアにやるならAPI Managementのauthentication-managed-identityポリシーでresource=\u0026quot;2ff814a6-3304-4ab8-85cb-cd0e6f879c1d\u0026quot;を指定する 前提条件 Databricks, API Managementは以下の設定で作成済みとする。\nDatabricks マネージドVNetではなくカスタムVNetで作成されていること 参考: https://learn.microsoft.com/ja-jp/azure/databricks/scenarios/quickstart-create-databricks-workspace-vnet-injection 必須ではないが、カスタムVNetで立てた方がネットワーク関連の設定がしやすい API Management システム割り当てマネージドIDが有効になっていること 参考: https://learn.microsoft.com/ja-jp/azure/api-management/api-management-howto-use-managed-service-identity Databricksとの通信をプライベートにする場合は、Developer, Standard v2またはPremiumプラン(送信VNet統合または内部/外部VNetインジェクションをサポートしているプラン)で作成すること 参考: https://learn.microsoft.com/ja-jp/azure/api-management/virtual-network-concepts 手順 バックエンドAPIを作成 まずはAPI ManagementからDatabricksへ接続するためのバックエンドAPI情報を設定する。\nAPI Managementの管理画面から「APIs」→「Backends」→「+追加」と進み、\nバックエンド(例: dbw-01)を作成する。\nランタイムURLにDatabricksワークスペースURLをぺたっと貼るだけでOK。\n(資格情報は後ほど設定する)\n次にAPI定義を作成する。\nAPI Managementの管理画面から「APIs」→「API」→「+Add API」と進み、\nAPI定義(例: dbw-01)を作成する。\n名前とAPI URL suffix(例: dbw-01)を設定するだけでよい。\n(Web service URLは設定せず、後ほどポリシー式で先ほど作成したバックエンドを指定する)\nAPI定義が作成できたら、「Design」→「All operations」→「Design」から全操作(メソッド)共通のポリシー編集画面を開き、\n先ほど作成したバックエンドを指定するset-backend-serviceポリシーを追加する。\nまた、リクエストを受信しバックエンドに流せるようにoperationを追加する。\nAzure Databricks REST API reference等を参考に必要なAPI操作を定義するのがよさげ。\n(OpenAPIで定義されたものがあればインポートできるんだけど、自分が探した限りだと見つけられなかった)\n今回は簡単に確認したいだけなので、全てのGETリクエストをそのままのパスで流すワイルドカードを設定する。\n(ドキュメントの注意事項にもあるように、本来は必要な操作だけをホワイトリスト的に定義した方が安全)\nこれでAPIを利用する準備ができたように見えるが(実際ほぼ終わり)、\n資格情報を設定していないので試しにAPI Management越しにリクエストを送っても失敗する。\n# 変数の設定 $ apimUrl=\u0026#34;\u0026lt;API Managementの管理画面「概要」→「基本」→「ゲートウェイのURL」\u0026gt;\u0026#34; # 例: https://apim-name.azure-api.net $ subscriptionKey=\u0026#34;\u0026lt;API Managementの管理画面「APIs」→「サブスクリプション」から取得した主キー\u0026gt;\u0026#34; # API Management越しにクラスター一覧APIを呼び出す(Databricks用のキーは付与せず、API Management用のキーのみ設定) $ curl -H \u0026#34;Ocp-Apim-Subscription-Key: ${subscriptionKey}\u0026#34; ${apimUrl}/dbw-01/api/2.1/clusters/list {\u0026#34;error_code\u0026#34;:\u0026#34;401\u0026#34;,\u0026#34;message\u0026#34;:\u0026#34;Unauthorized\u0026#34;} API ManagementのマネージドIDに権限を付与 次はこの資格情報を設定していく。\n参考: https://learn.microsoft.com/ja-jp/azure/databricks/dev-tools/azure-mi-auth\nDatabricks側でPAT発行してそれをAPI ManagementバックエンドのAuthorizationヘッダーに設定、でもいいんだけど、\nPATってあんまり推奨されるものでもない(個人の権限ですべての操作が行われてしまう)のでできればマネージドIDでやりたい。\nこのため、まずはAPI Managementのシステム割り当てマネージドIDをDatabricksにサービスプリンシパルとして登録(ワークスペースへのアクセス権限を付与)する。\n(ドキュメントだとユーザー割り当てマネージドIDが推奨されてるけど、やってみたらシステム割り当てでもできちゃった)\nDatabricksワークスペースで「設定」→「IDとアクセス」→「サービスプリンシパル」→「Service Principalを追加」からシステム割り当てマネージドIDを新規追加する。\n記載するIDはMicrosoft Entra IDの管理画面から「管理」→「すべてのアプリケーション」から対象のマネージドIDの情報を開き、「アプリケーションID」の値を利用する。\n(マネージドIDが見つけづらい場合はAPI Management管理画面(portal)の「セキュリティ」→「マネージドID」→「オブジェクト(プリンシパル)ID」の値を取得し、アプリケーション一覧画面で検索する。)\n必要であれば特定の権限を持つグループに対象のサービスプリンシパルを追加しておくこと。\n(今回は雑にadminに追加してしまっているが、本番運用の際はちゃんと専用のグループと権限を設定した方が良い)\nこれでAPI ManagementのマネージドIDでDatabricksワークスペースを操作する権限が設定できた。\nAPI Managementのポリシー設定 マネージドIDに権限を付与できたので、\n次はAPI Managementがアクセストークンを取得するためのauthentication-managed-identityポリシーを定義する。\nresourceに何を指定すればよいのか迷うところだが、\ncurlでアクセストークンを取得する例を見る感じ2ff814a6-3304-4ab8-85cb-cd0e6f879c1d(DatabricksのプログラムID)でよさそう。\n(正直ここに気付けなくて1日くらい溶かした)\nここまでできたら接続設定は完了。\nバックエンドAPIの作成後に試した手順で再度リクエストを送ると、\n今度は権限が付与されているので成功するようになっている。\n$ curl -H \u0026#34;Ocp-Apim-Subscription-Key: ${subscriptionKey}\u0026#34; ${apimUrl}/dbw-01/api/2.1/clusters/list {\u0026#34;next_page_token\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;prev_page_token\u0026#34;:\u0026#34;\u0026#34;} # クラスターを作成していないのでほぼ空っぽだが、APIリファレンス通りのレスポンスが返っているので動作確認は成功 # https://docs.databricks.com/api/azure/workspace/clusters/list やったぜ。\nこれでAPI Managementにさえ疎通できればDatabricksワークスペースAPIが使えるようになった。\n後は必要に応じてネットワークを閉域化したり、お好みで\u0026hellip;\nbicep 今回ブラウザでポチポチした操作をコード(bicep)化するとこんな感じ。↓↓↓\nネットワークまわりの設定で追加したところがあり、\nAPI Managementは外部モードでデプロイして、\nDatabricksにはプライベートエンドポイント経由で接続するようにした。\nまた、Databricksワークスペースの細かい設定(IPアクセスリストだったりサービスプリンシパル)はbicepでは書けないっぽいので(Terraformだといけるかも?)、\n手動で実施する必要がある。\nおわり Azure Databricksが難しすぎて日々泣いている。😭\nファーストパーティサービスと言いつつRBACはAzureロールと別物だし、サービスエンドポイントはないし\u0026hellip;\n同じように泣いている人の助けになれば幸い。\nおまけ ","date":"2024-11-13T00:00:00Z","image":"/post/2024-11-13-azure-databricks-api-management/sotochan.jpeg","permalink":"/post/2024-11-13-azure-databricks-api-management/","title":"Azure DatabricksワークスペースAPIをAPI Management経由で利用する"},{"content":"そとちゃん 病院 新居の近くに動物病院があるので、\nご挨拶ついでにワクチンを打ちにいった。\n(毎年この時期に3種混合ワクチンを打っている)\nそとちゃんは初めての病院でもおとなしく診察を受けててえらかった。👏\n診察室を勝手に探検するやんちゃは卒業したっぽい。\n体温はいつも通り38℃で正常。\n体重は環境が変わっても変わらず4.0kgをキープ。\n(ごはんちゃんと食べないのに\u0026hellip;🤔)\n毛並みと地肌もとても綺麗とのこと。\n(全然ブラシさせてくれないのに\u0026hellip;🤔)\nとりあえず触診は問題なしだった。\nちょっと問題だったのは歯肉炎。\n去年抜歯した右側の歯肉炎がひどいとのこと。\n(写真撮り忘れた)\n一応毎日歯みがきシートで拭いてはいたが、\n右側はかなり嫌がるのもあってなかなかうまく掃除できていなかったっぽい。😭\n普段からおとなしく掃除させてくれる左側の歯肉は綺麗だったので、\nもうちょっと歯みがきをがんばろう\u0026hellip;\nワクチンはいつも通り数秒で打てたので、\nついでに血液検査もしてもらった。\n引っ越し後初めての病院は約2時間で終了。\n家からかなり近くて助かっちゃった。\nごはん問題 めっちゃ好き嫌いする食のこだわりが強いそとちゃん。\nある日突然ウェットフード(黒缶)を食べなくなってしまった。😭\nここ数年のそとちゃんのごはんはずっとこんな感じだった。\nタイミング ごはん 食べ方 朝 ロイヤルカナン 消化器サポート(ドライ) 10g すぐ完食 昼 朝と同じカリカリ 10g ちょっとだけ食べて残す\nめんどくさいと全然食べない 夕 黒缶パウチ 1袋\n+トッピング(かつお節 or ちゅーる) 半分くらい食べて残りを朝までだらだら食べる 寝る前 にぼし 1,2本\nかにかま 少々 爆速で食べる 現在はこんな感じ。\nタイミング ごはん 食べ方 朝 ロイヤルカナン 消化器サポート(ドライ) すぐ完食 昼 朝と同じカリカリ 10g すぐ完食 夕 黒缶パウチ 1袋\n+トッピング(かつお節 or ちゅーる) トッピングだけ食べて全残し 寝る前 にぼし 1,2本\nかにかま 少々 爆速で食べる\n+足りないと文句を言う どうして\u0026hellip;😭\nおやつやトッピングは喜んで食べるので、\n食欲がないわけではなさそう。\n(なんならおやつはもっとよこせと鳴く😭)\n病院に行ったときにも相談したが、\n「他のごはんやおやつをちゃんと食べるなら単純にウェットだけ飽きたんじゃね？」とのことだった。\n確かに今まで残しがちだったお昼のカリカリを完食するようになっているので、\n(今まではカリカリ食べなくて悩んでたくらいなのでこれにはかなり驚いた)\n本当に黒缶に飽きてしまったのだろうか\u0026hellip;?\n一応飽きがこないように同じ黒缶でも毎日違う味を4~5種類ローテしていたのだが\u0026hellip;😭\nあまりにも黒缶を残すので、\n夜食用に追加でカリカリを出したらそれはちゃんと食べてしまった。\nどうしよう\u0026hellip;\n血液検査 病院に行ってから1週間ほどで血液検査の結果が返ってきた。\n果たして結果は\u0026hellip;?\nすこぶる健康だった。\n昔ちょっと高い数値が出たことのある腎臓系(クレアチニン)も正常範囲内で、健康優良児とのこと。\nこの調子ならシニアねこでも半年に1回くらいの診察で問題ないらしい。\nよかった〜\n(ごはんちゃんと食べないのに\u0026hellip;🤔)\nおわり 新しい動物病院も雰囲気がよくていい感じだった。\n健康とはいえ、年齢的に精密検査(猫ドック)もおすすめされたのでまた半年後に受けてみようかな\u0026hellip;?\nごはん問題は結構深刻。\nおやつだけお腹いっぱい食べたいそとちゃん\nvs\n黒缶を食べて欲しい俺\nの家庭内戦争が毎晩発生しているので(俺が全敗)どうにか解決せねば\u0026hellip;\nおまけ ","date":"2024-10-30T00:00:00Z","image":"/post/2024-10-30-sotochan/sotochan.jpeg","permalink":"/post/2024-10-30-sotochan/","title":"9月のそとちゃん(2024)"},{"content":"そとちゃん 新居を満喫 引っ越ししてそこそこ日数か経った。\nそとちゃんは新居に慣れて満喫中。\n(初日から我が物顔で振る舞っていたような気もするが)\n前の物件よりも部屋数が多いので、\nいろんな部屋でごろごろしているそとちゃんが見られて楽しい。\nこうして見ると右頬を下にして寝てることが多い？\nそうでもなかった。\nデカいダンボール 作業用のディスプレイを新しく買ったのだが、\nその空き箱がそとちゃんのものになってしまった(定期イベント)。\nディスプレイが約32インチだったのでこの箱が結構でかい。\nそとちゃんと比べても十分な(?)大きさ。\nこれくらい大きいものが置いておけるのも部屋が広くなった恩恵か\u0026hellip;?\n家が広くなったのに 繰り返しになってしまうが、\n引っ越しして家がめちゃめちゃ広く、部屋数も多くなった。\nそとちゃんも部屋をうろうろ移動したりして楽しんでいた。\nはずだった\u0026hellip;\n現在。\nそとちゃんはほとんど俺の仕事部屋(せまい)で過ごしている。\nなんで?\n俺が日中ほとんど仕事部屋にいるので、一緒に居たいのかしら🤔\n(そうだったらうれしいし健気でかわいい)\n前の家でも滅多にやらなかった、\n作業の邪魔もしてくる。\nかわいいね。\n右目 じめじめしてたせいもあるが、\nあんまり右目の調子が良くなかった。\n目ヤニが出てたらこまめに拭いてはいるものの、\nどうしても粘度が強くて地肌に張り付いてしまうので結構痛そうに見える\u0026hellip;😭\n毎年涼しくなると症状が軽くなるので、はやく治ってほしい。\nおわり ついに1ヶ月遅れですら更新できなくなってしまった😭\n9月分こそは\u0026hellip;\nおまけ ","date":"2024-10-04T00:00:00Z","image":"/post/2024-10-04-sotochan/sotochan.jpeg","permalink":"/post/2024-10-04-sotochan/","title":"8月のそとちゃん(2024)"},{"content":"そとちゃん ひっこし 実は(?) 引っ越しをしていた。\nこれまで住んでいた物件は日当たりもよく、\n立地や設備も文句なしだったのだが\u0026hellip;\n諸事情(※)により住めなくなってしまったので、\n仕方なくお引っ越し。😭\n(※何がきっかけで叩かれるかわからないので私個人の名誉のために強調しておくが、決して近隣トラブルや契約の不備などではなく別の要因によるものである)\nそとちゃんと暮らし始めてからこれで3回目の引っ越し。\n環境が変わることでストレスにならないだろうかと(一応)心配だったが、\nやはり流石のそとちゃん、ノリノリである。\nまずは荷造りで大興奮。\n荷物を作るたびにできる部屋の隙間と、\n段ボールが楽しくてしょうがない。\n引っ越し当日はやっぱり大騒ぎ。\n車移動が苦手なそとちゃんはわんわん鳴いていた。\n(今回はこれまでで最も移動距離が長いのでかわいそうだった\u0026hellip;😭)\n新居についたら早速探検。\n知らない場所でも全然ビビらない性格なのは本当に助かる。\nストレスで吐いたり粗相もなかった。\n引っ越し業者さんに絡んだり、\n部屋を一通りチェックして大満足。\n部屋。\nそう、新居は部屋がめちゃめちゃ広い。\nどれくらいかというと、今まで住んでた部屋の倍くらいの面積となっている。\nこれまでは縦長1Kの部屋だったので直線でしか走れなかったのが、\n新居は正方形に近く、部屋数も増えたので、いろんな方向に曲がれたりと走り甲斐があるようだ。\n日当たりもよくてねこ大満足。\nそんなわけで、今回の引っ越しは大きなトラブルもなく無事に終わった。\nおつかれさまでした。\nおわり そんなこんなで最近はドチャクソ忙しかったのだが、なんとか落ちついてきた。\n(おかげで書くのがめちゃめちゃ遅くなったが😭)\n新居は広くなったぶん家賃も💸倍増💸してしまったが、\nねこが広い部屋で楽しそうなのでまあヨシ！ 😭\n(リアルフレンドの皆様は是非泊まり/遊びに来ていただきたい。ねこも喜ぶので\u0026hellip;)\nおまけ ","date":"2024-08-31T00:00:00Z","image":"/post/2024-08-31-sotochan/sotochan.jpeg","permalink":"/post/2024-08-31-sotochan/","title":"7月のそとちゃん(2024)"},{"content":"まとめ Databricks CLIをインストールしてなくても、直接APIを叩いてワークスペースのIPアクセスリストを設定できる 経緯 Azure Databricksのワークスペースについて、\nインターネットからアクセスしたいが接続元のIPアドレスは制限したい、ということがあった。\n(VPN引いてプライベート接続はお金かかるからあんまりやりたくなかった)\nIPアクセスリストの設定は公式の設定手順はあるが、\nDatabricks CLI前提の手順になっている+自分の都合によりどうしてもCLIのインストールができないという状況なので、\nAPIをcurlで叩いて設定してみた。\n手順 ワークスペースURLを取得 APIをたたくのに必要なのでワークスペースURLを取得する。\n方法はなんでもいいけど、自分はAzure portalからコピーした。\n# ワークスペースURL $ url=\u0026#34;https://adb-00000000000000.00.azuredatabricks.net\u0026#34; アクセストークンの取得 APIを叩くためのアクセストークンを取得する。\nhttps://learn.microsoft.com/ja-jp/azure/databricks/dev-tools/auth/pat#--azure-databricks-personal-access-tokens-for-workspace-users\n# アクセストークン $ token=\u0026#34;dapixxxxxxxxxxxxxxxxx-3\u0026#34; IPアクセスリストの作成 IPアクセスリストを作成する。\nhttps://docs.databricks.com/api/azure/workspace/ipaccesslists/create\n# リクエストボディJSON (ip_addressesにアクセスを許可したいIP(v4)のリストをCIDR表記で記載する) $ cat \u0026lt;\u0026lt; EOF \u0026gt; ip-access-list.json { \u0026#34;label\u0026#34;: \u0026#34;My Office\u0026#34;, \u0026#34;list_type\u0026#34;: \u0026#34;ALLOW\u0026#34;, \u0026#34;ip_addresses\u0026#34;: [ \u0026#34;133.xxx.xxx.xxx/32\u0026#34; ] } EOF # Create access list APIをコール $ curl -X POST -H \u0026#34;Content-Type: application/json\u0026#34; -H \u0026#34;Authorization: Bearer $token\u0026#34; \u0026#34;$url/api/2.0/ip-access-lists\u0026#34; -d \u0026#39;@ip-access-list.json\u0026#39; {\u0026#34;ip_access_list\u0026#34;:{\u0026#34;list_id\u0026#34;:\u0026#34;ec88d280-02f6-4945-84b8-76a7bcdeb05f\u0026#34;, \u0026#34;label\u0026#34;:\u0026#34;My Office\u0026#34;, \u0026#34;ip_addresses\u0026#34;:[\u0026#34;133.xxx.xxx.xxx/32\u0026#34;],\u0026#34;address_count\u0026#34;:1,\u0026#34;list_type\u0026#34;:\u0026#34;ALLOW\u0026#34;,\u0026#34;created_at\u0026#34;:1724834759440,\u0026#34;created_by\u0026#34;:1878671197505866,\u0026#34;updated_at\u0026#34;:1724834759440,\u0026#34;updated_by\u0026#34;:1878671197505866,\u0026#34;enabled\u0026#34;:true}} ちなみに、\n一番最初に作るIPアクセスリストの範囲に現在の自分のIPアドレスが含まれていない場合は怒られてしまうので注意。\n(おそらく現在の端末から操作できなくなるのを防ぐため。\n自分のIPアドレスからの通信を許可するリストが既に存在すれば、\n2つめ以降のリスト作成時には自身のIPが含まれていなくても問題なし。)\n自分のIPアドレスは https://checkip.amazonaws.com/ とか、なんか適当に調べれば出てくるはず。\n# ip-access-list.jsonのip_addressesに現在の自分のIPアドレスがない場合 $ curl -X POST -H \u0026#34;Content-Type: application/json\u0026#34; -H \u0026#34;Authorization: Bearer $token\u0026#34; \u0026#34;$url/api/2.0/ip-access-lists\u0026#34; -d \u0026#39;@ip-access-list.json\u0026#39; {\u0026#34;error_code\u0026#34;:\u0026#34;INVALID_STATE\u0026#34;,\u0026#34;message\u0026#34;:\u0026#34;Your current IP 133.xxx.xxx.yyy will not be allowed to access the workspace under current configuration\u0026#34;} IPアクセスリストを有効化 IPアクセスリストでのアクセス制限を有効化する。\nhttps://docs.databricks.com/api/azure/workspace/workspaceconf/setstatus\n# Enable features $ curl -X PATCH -H \u0026#34;Content-Type: application/json\u0026#34; -H \u0026#34;Authorization: Bearer $token\u0026#34; \u0026#34;$url/api/2.0/workspace-conf\u0026#34; -d \u0026#39;{\u0026#34;enableIpAccessLists\u0026#34;: \u0026#34;true\u0026#34;}\u0026#39; 以上の手順が正しく実施できていれば、\nIPアクセスリストにないIPアドレスからワークスペースを開こうとするとちゃんと拒否されるようになる。\nやったぜ。\n一応、IPアクセスリストの状態を確認する方法も貼っておく。\nhttps://docs.databricks.com/api/azure/workspace/ipaccesslists/list\nhttps://docs.databricks.com/api/azure/workspace/workspaceconf/getstatus\n# Check configuration status $ curl -s -H \u0026#34;Authorization: Bearer $token\u0026#34; \u0026#34;$url/api/2.0/workspace-conf?keys=enableIpAccessLists\u0026#34; | jq { \u0026#34;enableIpAccessLists\u0026#34;: \u0026#34;true\u0026#34; } # Get access lists $ curl -s -H \u0026#34;Authorization: Bearer $token\u0026#34; \u0026#34;$url/api/2.0/ip-access-lists\u0026#34; | jq { \u0026#34;ip_access_lists\u0026#34;: [ { \u0026#34;list_id\u0026#34;: \u0026#34;ec88d280-02f6-4945-84b8-76a7bcdeb05f\u0026#34;, \u0026#34;label\u0026#34;: \u0026#34;My Office\u0026#34;, \u0026#34;ip_addresses\u0026#34;: [ \u0026#34;133.xxx.xxx.xxx/32\u0026#34; ], \u0026#34;address_count\u0026#34;: 1, \u0026#34;list_type\u0026#34;: \u0026#34;ALLOW\u0026#34;, \u0026#34;created_at\u0026#34;: 1724834759440, \u0026#34;created_by\u0026#34;: 1878671197505866, \u0026#34;updated_at\u0026#34;: 1724834759440, \u0026#34;updated_by\u0026#34;: 1878671197505866, \u0026#34;enabled\u0026#34;: true } ] } (設定が終わったらアクセストークンの無効化を忘れずに\u0026hellip;)\nおわり 最近生成AIに頼りすぎで、なんか久しぶりにこういうの書いた気がする。\nたまにはいいな〜\nおまけ ","date":"2024-08-28T00:00:00Z","image":"/post/2024-08-28-azure-databrics-configure-ip-access-list-with-api/sotochan.jpeg","permalink":"/post/2024-08-28-azure-databrics-configure-ip-access-list-with-api/","title":"Azure Databricksワークスペースへアクセス可能なIPアドレスを制限する(curlで設定)"},{"content":"そとちゃん カリカリ 5月末に買った新しいカリカリマシーンがいい感じ。\n6月は結構じめじめしていたけど、\n湿気対策のおかげでカリカリがちゃんと乾いた状態をキープできていた。\nそとちゃんも結構食べてくれていて、完食することも多くなってきた。\n一緒に暮らして6年目にしてようやく、カリカリがしけて全然食べない問題がついに解決したっぽい。\nやった〜🎉\n(なお、俺が居るとあまり食べないのは変わらず\u0026hellip;)\nあつい 夏が始まった。\n暑くてたまらない。\nでもそとちゃんは毎日ひなたぼっこ\u0026hellip;\nもちろんエアコンはガンガンに回してるけど、それでも窓際は暑いだろうに\u0026hellip;\n流石に1日中窓際という感じでもなく、\nたまに廊下に落ちてることもある。\nだらけきっているねこはとてもかわいい。\n溶けてる顔がたくさん見られてかわいいけど、\nそれでもやっぱり早く涼しくなってほしい\u0026hellip;\nおわり ちょっといろいろありすぎて、意味わかんないくらい遅筆になってしまった。\n来月こそは\u0026hellip;\nおまけ ","date":"2024-07-25T00:00:00Z","image":"/post/2024-07-25-sotochan/sotochan.jpeg","permalink":"/post/2024-07-25-sotochan/","title":"6月のそとちゃん(2024)"},{"content":"そとちゃん 8さい 5月生まれ(推定)のそとちゃん。\nついに8さいになった。\n当然本人(にゃん)にその意識はないが、\nおめでたいので勝手にお祝いさせてもらった。\n去年はキャットタワーだったが、今年はおもちゃをプレゼント。\nまずはでるでる自飯器。\nこれはびっくりするくらい食いつかなかった\u0026hellip;😭\nお次は電池で動く虫のおもちゃ。\n残念なことに、おもちゃの仕組み上カーペットの上だと全然動かなかった。\nこれもハズレ\u0026hellip;😭\nと思ったらそとちゃんは気に入った様子。\n動けないけどじたばたしている虫にちょっかいを出すのが楽しいらしい。\nあとはいつものまたたびスプレーと、\nいつものカサカサする猫じゃらしをプレゼント。\n最後はいつもよりちょっとおやつ多めのごはんでお祝い。\nよかったね〜〜🎉\n記念写真 もう毎年恒例となった誕生日の記念写真。\n今年もいつもの写真屋さんで撮ってもらった。\nスタジオに放たれたそとちゃんはやっぱり大冒険\u0026hellip;\n小道具や機材を思う存分存分探検してから撮影開始。\n今年のそとちゃんはあんまり集中力がなくて、\nカメラマンさんも大変そうだった😹\n目線を釣るためのおもちゃを持っていったのだが、\nそとちゃんがあまりにも熱中してしまい逆効果だった😭\nこんなこともあろうかと、\nここで秘密兵器のまたたびを投入！\n\u0026hellip;今度はまたたびを持つ俺を追いかけてしまい、\nそとちゃん単体でうまく撮れない😭\n(画角に俺の手が入ってしまった)\n結局今回はほとんどカメラ目線を貰えずに時間(集中力)切れ\u0026hellip;\nつくづくねこ様は人間の思い通りにならないことを再認識した。\nでも懲りずにまた来年も撮りに行こう\u0026hellip;\n風呂場 最近暑いから？そとちゃんがよく風呂場で遊んでいた。\nそとちゃんが入れないように普段湯船に載せている風呂フタを立てて乾かしていたら、\nいつのまにか侵入されていた。\n一度入った湯船の中が気に入ってしまったようで、\n何回引っ張り出してもまた入るようになった😹\n自分が濡れないのを良いことに、\n完全に調子に乗っている。\n今の家に来てからあまりお風呂に入れてないので、\n水が出る場所ということもわかってないのかもしれない😂\n夏も近いし、久しぶりにシャンプーしちゃおうかしら\u0026hellip;\nカリカリマシーン 今まで4年くらい使っていたカリカリマシーンSPがついにお亡くなりになってしまったので、\n久しぶりに自動給餌器を買い換えることにした。\n最近はいろんなメーカーのものが出ているが、\n結局今まで使っていたメーカーの最新機種を購入。\nカリカリマシーンV2C\nこれが数年でかなり進化していて、\nタンクの密閉度が上がり、乾燥剤用のポケットが内側についたのがめちゃめちゃ嬉しい。\n今まではタンクの内側にテープで無理やり乾燥剤を取り付けたり、\nタンクをラップで塞いで蓋に本を乗せたりと苦しい湿気対策をしていたが、\nこれで梅雨の時期にそとちゃんがカリカリ食べない問題が解決するかも\u0026hellip;?\nあとはいきなり機械が変わってそとちゃんがびっくりするかとも思ったが、\n特に気にせず普通に食べてくれたのでよかった。\nこういう図太いところは本当に手がかからなくて助かる\u0026hellip;\nこれでカリカリいっぱい食べてくれ〜🙏🙏🙏\nおわり そとちゃんが8さい(推定)になった。\nたぶん3さいでうちにお迎えしたので、\nこれでもう5年一緒に暮らしていることになる。\nそとちゃんはどう思ってるかわからないが、\n俺は毎日一緒にいられて楽しく幸せに過ごしているので、あっという間の5年間だった。\nここまで大きな病気もなく、まだまだうるさいくらいに元気だけど、\nシニアねこであることには違いないのでこれまでよりも一層健康には気をつけていきたい。\nそとちゃんの猫生なのであまり無理強いするつもりはないんだけど、\n個人的には30さいのおばあにゃんになるまで元気でいてほしい\u0026hellip;👵🐈\nまずは元気な9さいを目指してがんばろう。\nおまけ ","date":"2024-06-06T00:00:00Z","image":"/post/2024-06-06-sotochan/sotochan.JPG","permalink":"/post/2024-06-06-sotochan/","title":"5月のそとちゃん(2024)"},{"content":"まとめ 右目 冬の間調子が良かったそとちゃんの右目。\n最近少し暖かくなってきて、\nまた目ヤニがひどくなってしまった。\nこまめに専用のウェットシートで拭いているのだが、\nこれがそとちゃんは気に入らない。\n(昔は嫌がらず拭かせてくれたのに\u0026hellip;)\nとはいえ目ヤニを放置するわけにもいかないのでなんとか耐えてもらう。\n毛の根元にこびりついた目ヤニがなかなか取れないのだが、\n強く拭くと肌がすぐ赤くなってしまうので、これがとても難しい\u0026hellip;\n月末ごろから少し目ヤニが収まってきてまたいい感じなので、\nどうにかこの調子が続くとそとちゃんも俺も助かる。\n毎年梅雨の時期が一番ひどいので、そうならないことを祈りたい。\n箱 3月に通販でいろいろ買う機会があり、\n久しぶりにそれなりの大きさの段ボールが手に入った。\n袋派なはずのそとちゃん、意外にもこれに食いついた。\nこのごろ部屋に箱を置いてなかったので珍しいのかもしれない。\nよく入ってガサゴソしている。\nだいぶ箱が好きになったのか、\nねこ用ではない(ねこ用段ボールとは?)箱をたまたま放置していたらそれにも入ってくれた。\nかわいい。\n\u0026hellip;\n放置していた袋(これもねこ用ではない)にも入ってくれた。\nかわいいね。\nユニフォーム 我が千葉ロッテマリーンズにソト選手が入団した。\nそれだけ。\nこたつ 4月は新生活の時期ということで、\n出しっぱなしだったこたつ布団を撤去することにした。\n正直この冬はエアコンを多用していてあまり電源を入れなかったので、\nそとちゃんもあっさり受け入れてくれるかと思ったが\u0026hellip;\nそこはねこが相手なので、思い通りにはいかない。\n湯たんぽを入れた時以外入ってなかったくせに\u0026hellip;\n十数分の説得(※物理含む)の末、\n布団の撤去に成功。\n部屋はだいぶスッキリしたが、\nそとちゃんはちょっといじけてしまった。\n外した布団に恨めしそうに乗る姿がなんとも言えない。\n湯たんぽは残すので、どうにか機嫌を治してほしいところ\u0026hellip;\nおわり ねこにとってかなしい出来事はちょこちょこあったが、4月もそとちゃんは楽しそうだった。\n多少嫌なことがあっても自分ですぐ機嫌を治せてえらいな〜〜！\n5月には誕生日(元野良なので推測)が来るが、変わらず元気で楽しく過ごしてほしい。\n(これを書いてる時点ではもう8歳になりました)\nおまけ ","date":"2024-05-02T00:00:00Z","image":"/post/2024-05-02-sotochan/sotochan.jpeg","permalink":"/post/2024-05-02-sotochan/","title":"4月のそとちゃん(2024)"},{"content":"まとめ いたずら そとちゃんがまた仕事机に登るようになってしまった。\nここ数年はパソコンの邪魔をすることもあまりなかったのに、\n何がきっかけかわからないけど最近になってまたいたずら三昧。\nマウスやイヤホン、紙類をちょいちょいいじるのが楽しくてしょうがない。\nかわいいおててでしっかり床に落とす。\n仕事中にも乗ってくるのですごい迷惑。\n降ろしてもめげずにまた登ってくる。\nこまった\u0026hellip;\n爆鳴き 最近のそとちゃんは机の上に登るだけじゃなくて、めちゃめちゃ鳴く。\nとてもうるさい。\n特徴的なのが、わざわざ俺から離れたところで爆鳴きすること。\n仕方ないので席を立って撫でにいくとすごい満足そうにして、\nまた離れた場所に移動して再度爆鳴きする。\nすごいかまってちゃん。\n疲れれば大人しくなると思っておもちゃを投げてもあんまり遊んでくれない。\n本当にこっちの気を引きたいだけらしい\u0026hellip;\nあとは仕事で通話してるときにすごく積極的に発言する。\nたまに研修用の動画にも反応するので、たぶん人間の声に反応するのが好きなんだと思う。\n元気なのは良いことだけど、\n仕事中はもうすこしちゃんとお昼寝してくれると助かるなあ\u0026hellip;\nおわり そとちゃんは相変わらず元気いっぱい。\nというか年々わんぱくになってるような気がする。\n今年で推定8歳になるはずなんですが、シニアねことは\u0026hellip;?\nおまけ ","date":"2024-04-19T00:00:00Z","image":"/post/2024-04-19-sotochan/sotochan.jpeg","permalink":"/post/2024-04-19-sotochan/","title":"3月のそとちゃん(2024)"},{"content":"まとめ 見張り 2月は窓につけてあるねこ用ハンモックがそとちゃんのお気に入りだった。\nここにはそとちゃんが好きなブランケットを敷いているので、\n寒い日でも全然平気らしい。\nブランケットだけでなく、\nもちろん外の景色を見るのも大好き。\n毎朝欠かさず、日が登るのを監視している。\n毎朝欠かさず。\nそれはすなわち、寝ている俺を毎朝踏み台にしているということ。\nベッドとハンモックの間は別に自力で跳べる高さのはずなのに、\nわざわざ寝ている俺の腹の上に乗ってからジャンプして登っている。\n体重4kgしかないそとちゃんとはいえ、\nたった数cm程度の足で全体重をかけて踏み切られると結構重いし痛い。\n踏み台にするだけならまだしも、\n降りるときも最近は躊躇なく俺の腹に着地してくる。\n昔はそんなひどいことしなかったのにどうして\u0026hellip;\nもう一個低いハンモック買おうかな\u0026hellip;\nしろたん そとちゃんが一番すきな寝床は、本棚の上のしろたん。\n夜中に寝床を移動することはあるが、だいたい最初はここで眠りについて朝もここで眼を覚ます。\nが、このしろたんもだいぶ汚れてきていて、\n色もグレーになり、なによりも毛だらけ\u0026hellip;\nというわけで久しぶりにしろたんを洗うことにした。\n決死の説得も虚しく武力衝突が起こってしまったが、\nなんとか汚れたしろたんを回収。\n抱きぐるみを洗うといっても着脱式なので、\nカバーを外して洗濯機で回すだけで綺麗になった。\nここで(予想通り？)問題が発生。\nそとちゃん7歳(ねこ)、多感なお年頃、洗われたしろたんが気に食わない\u0026hellip;\n勝手に洗われたことに怒っているのか、自分の匂いが消えて不安なのか、\n全然使ってくれなくなってしまった。\nこれまで引っ越しや病院でもすぐに新しい環境に適応し、\nねこらしからぬ度胸を見せていたそとちゃんがここまで露骨に落ち込むのを見るとかわいそうになってくる。\nそれでもいつまでも乗らないと匂いもつかないので、\n何回か抱っこして無理やり乗せてみた。\nが、ダメ\u0026hellip;\nと思ったらあれ？\nある日気づくとあっさり寝ていた。\nいったいなにがきっかけで気に入ったのか\u0026hellip;?\nよくわからないけど使ってくれてよかった。\nとはいえ今回あまりにもかわいそうだったので、今後はもう洗わないかな\u0026hellip;\nおわり ちょっと身の回りでいろいろあってクッソ遅筆になってしまった。\n次回こそは\u0026hellip;\nおまけ ","date":"2024-03-19T00:00:00Z","image":"/post/2024-03-19-sotochan/sotochan.jpeg","permalink":"/post/2024-03-19-sotochan/","title":"2月のそとちゃん(2024)"},{"content":"まとめ 最強の資格SSWAを受験した。その体験記。\nSSWAとは IT系のエンジニアとして働く上で資格いるいらない論争はたまによくある。\n「資格なんて要らん！実務での経験が重要だ！」という方は多いだろうし、\n履歴書盛るために資格だけやたらと持ってる未経験者、みたいなのは自分も「？」と思うことはあるが、\n「最低限の学習意欲の証明にはなるので無いよりはマシ、ただし持ってるから偉いということはない。」\nくらいに考えている。\n実際自分もいくつか資格は持っているし、\n雰囲気で触っていたものについて体系的に学び直せることはメリットが多いと思うので、\n年に数回ほど新しいものを取得している。\nそこで重要になってくるのが「どの資格を選ぶのか」だと思う。\nI○A, ○isco, Ora○le, AW○, CN○F, etc\u0026hellip;\n世の中にはたくさんの資格がある。でも人生の時間は限られている。\n学ぶなら自分にとって本当に価値のある資格を選ぶべきだ。\nそんな中で今回私が選んだのが、SSWA(Sauna Spa Wellness Advisor) だ。\nSSWA1とはJSSA(Japan Sauna Spa Association)2が認定している、\nサウナやスパに関する正しい知識の活用を目的とした温浴施設の従業員や利用者向けの資格である。\nつまり俺みたいなサウナ好きのおっさんのための最強の資格だ。\nなんとあの厚生労働省が支援しているらしい。\n(支援って具体的に何？？)\n今回はその受験記である。\n金さえ払えば取れるガバ資格とか怪しい利権団体とか言うやつは俺がぶっとばす。\n(技術要素は)ないです\n受験申し込み どんな資格も受験の申し込みをしないと始まらない。\nまずは公式サイトにアクセスする。\nなんと安心感のあるWebページだろうか。\nスクリプトは必要最小限、適度なイラストと必要な説明だけが記載されておりとても美しい。\nテキストの内容をチラ見することもできる。\n俺こういうの大好き。\nちなみにこのページ、なんと英語対応もされている。\n画面右上のEnglishをクリックすると英語版ページが表示される。\n英語版はHTMLほぼ直書きの漢らしいページだ。\nこういうのでいいんだよ。\n余談はこれくらいにして、\n受験の申し込みは公式サイト下部のお申込みフォームから行える。\nリンク先のフォームで下記の項目を入力して送信するだけで手続きは完了だ。\n名前(ふりがな) 住所 電話番号 メールアドレス そう。\nクレジットカードを登録する必要がないのである。\nではどうやって受験料を支払うのだろうか？\n代引きである。\nこの令和の時代に代引き。\n万が一のサイバー攻撃にも備えてクレカ情報を自社側で持たないようにするセキュリティ意識の高さが伺える。\nちなみに代引きの支払いにはクレジットカードが使える。便利。\nそういえば受験料は代引き手数料込みで5330円だった。\n受験料がアホみたいに高いベンダー系の試験に比べてなんとお手頃だろう。\nただ、この代引きというのは厄介でもある。\n宅配ボックスは使えず、支払いのために直接受け取らないといけないのだから不便だ。\n俺の場合は幸運なことに在宅勤務なので無事に一発で受け取れたが、\n特に1人暮らしで毎日出勤する社会人の場合はこの受け取りこそが本資格で最も難しい問題だと言えよう。\n今回は申し込みから5日くらいで封筒が届いた。\n中身はこんな感じ。\n公式テキスト 試験問題 返信封筒 ここで気になるのが返信封筒。\n宛先の物件情報をみると築39年のオフィスビル、約10坪ほどの事務所で運営されているらしい。\nそれほど人数の多い団体ではなさそうだ。\n怪しい利権団体とか言うやつは俺がぶっとばす。\n試験対策 SSWA試験の流れとしては\nテキストを読む 試験問題を解く(制限時間60分) 回答用紙を記入して郵送 というかんじ。\n驚いたのは試験官など監視の仕組みがないこと。\nこれではカンニングし放題、制限時間破り放題(?)ではないか。\n否。\nサウナを愛する人間は皆善人である。\nサウナを愛する人間がカンニングなどするはずがない。\nカンニングして合格するような人間にサウナを愛する資格はない。\nJSSAはそう言うことが言いたいんだと思う。\n金さえ払えば取れるガバ資格とか言うやつは俺がぶっとばす。\nというわけでテキストを真面目に読んでいく。\nテキストはB5サイズ、63ページほどの冊子だ。\n内容はサンプルの通りで、\n三章構成となっている。\n第一章　サウナ・スパ健康法 お風呂、サウナの効果や健康に良い入り方、サウナの歴史など 第二章　サウナ・スパ後のボディケア 入浴後に効果的なマッサージなど 第三章　サウナ・スパ施設内での応急手当 サウナやお風呂で人が倒れた際の救急隊員に引き渡すまでの処置手順 第一章はとても勉強になる。この試験のメインテーマと言って良いだろう。\n入浴法の具体的な手順まで紹介されていてタメになる。\n第三章の心肺蘇生法などは自動車教習所で教わるものとほぼ同じなのであまり深く読む必要はなさそうだが、\nやけどや脳貧血など、サウナならではの症状に応じた手当の方法は勉強になる。\n一番難しいのは第二章、特にPart.2の「温まった体に効果的なツボ指圧 20選」である。\n東洋医学のツボの名前と効能が列挙されており、これを一発で全て覚えるのはとても大変だ。\n他の章の内容と比較してもおそらく試験で難しい問題があるとしたらこのパートの内容だろうと考え、\n自分の場合はここを3周した。\n試験 1,2時間程度でテキストを読み終わったら早速試験問題に取り掛かる。\n公平性のために試験問題の内容全ては公開できないが、\nテキストの内容に関連する3択問題の合計25問で構成されている。\nテキストの指示に従い、60分タイマーをセット。\nバリバリ問題を解いていく。\n長らく味わってこなかった、エンピツで筆記問題を解く感覚。\n学生時代にはあまり好きではなかった暖かみのある作業が今はとても楽しい。\n問題の内容としてはテキストを読んでいれば正解がわかるものばかり。\nただし意地悪というほどでもないが、漢字や数値が違うだけの選択肢などがあり、\nケアレスミスには注意が必要である。\nちなみに自分がしっかり対策したツボの名前に関する問題は1つも出なかった。\n試験問題を使い回しているとは到底考えづらいが、\nある回の試験で1問も出なかった、というのは今後の受験生の判断材料の1つになると思い書いておく。\n回答にかかった時間は5分。\n制限時間60分とはなんだったのか。\nあとは回答用紙を封筒に入れて投函して試験は終了。\nあらかじめ切手が貼ってあるのがありがたい。\n結果 テストセンター等で受けるIT系の試験とは異なり、\n合否がわかるまでにはかなりの時間がかかる。\nきっと厳正な審査、採点がされていることだろう。\n自分の場合は回答を投函してから10日ほどで何やら緑色の大きい封筒が届いた。\n結果は\u0026hellip;?\n合格だった。\n合格のご褒美として以下のものを受領した。\n認定証 資格カード ピンバッジ 認定証は卒業証書みたいな良さげな紙でできている。\nあんまりこういうのもらったことないからうれしい。\n資格カードは免許証サイズ。\nこいつには後で重要な役割が待っている。\nピンバッジ\u0026hellip;\nこれいる？\nちなみに得点が何点だったかはわからないようになっていた。\n金さえ払えば取れるテキトー採点のガバ資格とか言うやつは俺がぶっとばす。\nSSWAのメリット このSSWA、合格して認定証をもらって終わりの資格ではない。\nなんと合格者は資格カードを見せると全国各地の協賛店で割引サービスが受けられるのだ。\nそれも名店とされるスパ、スーパー銭湯、カプセルホテルばかりである。\n正直なところこの特典目当てで受験した。\n約5000円の受験料も、そのうち元が取れるだろう\u0026hellip;\nと思いきや、 よく行く店舗は既に会員になってしまっていたり(割引の併用ができない)、\n俺がよく使う短時間コースは対象外だったりで、\n取得してから今日までまだ割引の恩恵を受けられていない。😭\nどうして\u0026hellip;\nおわり SSWAに合格した。 やったぜ。\n特典の恩恵は受けられていないが、\nこれまで雰囲気で入っていたサウナに対して学ぶ良い機会となった。\n1人のサウナ好きとして大いに自慢したい。\n俺は一般の人よりサウナに詳しいんだ。資格を取った俺は偉いんだ。\nまた、SSWAより上位の資格としてSSPM(Sauna Spa Professional Manager)というものがあるらしい。\nSSWA資格者しか受験できないということなので、機会があればぜひ受験したい。\n(なお受験料は3倍の15000円💸)\nおまけ サウナ・スパ 健康アドバイザー\nアルファベットの方がなんかかっこいいので本記事ではSSWAと呼称する。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n公益社団法人 日本サウナ・スパ協会\n同じく。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2024-02-23T00:00:00Z","image":"/post/2024-02-23-sswa/sotochan.jpeg","permalink":"/post/2024-02-23-sswa/","title":"SSWAを受験した"},{"content":"まとめ 湯たんぽ クリスマスプレゼントで献上したねこ型湯たんぽが結構いい感じ。\n期待していた通り、くっついて(乗っかって)仲良くしてくれている。\n横に並べてみると本当にねこが2匹いるみたいでめちゃめちゃかわいい。\n本当に買ってよかった\u0026hellip;\nただ、そとちゃんが自分から横に並ぶのは稀で、\nどちらかというと上に乗っかることが多い。\n寒いときに湯たんぽをひざに載せていると普通にそとちゃんが上から乗っかりにくる。\nたまに猫の上に乗る猫の動画とか見かけるけど、\nねこは下の子が重くてかわいそう的なことは考えないんだろうか\u0026hellip;？\nもしくは、そとちゃんはかしこいのでこれがねこじゃないことに気づいていて、\nクッション感覚で乗っているのかもしれない。\n毛繕いとかもしないし\u0026hellip;\nまあかわいければなんでもOKです。\nあまえんぼ 理由は謎だけど、\n最近そとちゃんの甘えがはげしい。\nいわゆる要求鳴きが多い。\nドアを開けて欲しいときとかにわんわん鳴く。\nドアはそとちゃんが開けられないからしょうがないんだけど、\nなんだか要求がエスカレートしてしまってついにはこたつの布団の前でも鳴くようになった。\nふとんを持ち上げろということらしい。\n元々自力で潜ってたのに\u0026hellip;\nこたつに入るときだけならいいものの、\n出たい時にも鳴くことがある。\n俺はドアマンか\u0026hellip;？\nあとはひなたぼっこしたいのに太陽が出てないときにもよく鳴く。\n俺が悪いみたいな雰囲気だしてくるけどこればっかりはどうしようもない。\n仕方がないのでおもちゃで遊んでごまかす。\n簡単にごまかされちゃうのもかわいい。\n一番のあまえんぼポイントはやっぱりひざに乗ってくるところ。\nソファでだらだらしてるとすかさず乗ってくる。\n一度ひざに乗ると長くて、\nだいたい10分くらい、長い時は30分以上乗っている。\n(不思議と脚はしびれない)\n長く居て欲しいときはなでたりおしりを叩いたりするといい感じになる。\n問題は早めに降りて欲しくなったときの手段がないこと\u0026hellip;\nおわり 年は明けたけど、そとちゃんは相変わらずのんびりだらだら過ごしている。\n毎日がお正月みたいなものでとてもうらやましい。\nそろそろ寒さもピークを過ぎるはずなので、\nまた近所で抱っこ散歩とかできるといいな\u0026hellip;\nおまけ ","date":"2024-02-02T00:00:00Z","image":"/post/2024-02-02-sotochan/sotochan.jpeg","permalink":"/post/2024-02-02-sotochan/","title":"1月のそとちゃん(2024)"},{"content":"まとめ カーテン そとちゃんはカーテンが好き。\nカーテンの薄い生地がきもちいいみたいで普段から裾をベッドの上に広げてあるのだが、\nよくここでゴロゴロしている。\nでもそとちゃんが自力でカーテンを持ち上げることはできないので、\n俺が裾をベッドに広げ忘れているとお昼頃にすごく文句を言ってきてうるさい。\nカーテン越しにちょっと光を浴びるのが好きなのかな？\nちょっと変なクセだと思うけどかわいい。\n毛布 今月はちょこちょこ人を泊めることがあったので、\n(ちなみにそとちゃんはお客さんが大好き!誰でもすぐに膝に乗る)\n普段使っていなかったもう1枚の毛布を引っ張り出した。\nすぐにそとちゃんに気に入られてしまった。\nあまりにもお気に入りなので、\nその後もそとちゃんが乗れるように畳んでソファに置いている。　当分の間片付けるつもりはないのに、\nそとちゃんが必死に場所を守ろうとしているのが健気でかわいい。\nやっぱりこたつ カーテン、布団、毛布とお気に入りスポットがたくさんあるそとちゃん。\nそれでもやっぱり寒い日はこたつが好き。\nあれ？ねこいないな？と思ったらだいたいこたつの中で寝ている。\n丸まってるおしりとこたつ布団の境目が個人的にめちゃめちゃかわいい。\nいいね〜\nクリスマス 今年もそとちゃんにサンタマントを着てもらった。\nめちゃめちゃかわいいねえ\u0026hellip;\nハロウィンの時のマントもそうだけど、\nやっぱり軽めの素材だとあまり気にならない様子。\n嫌がらずに協力してくれた。\nしっかりカメラ目線くれてたすかる。\n(おもちゃで釣ったけど\u0026hellip;)\nそんな今年のそとちゃんへのクリスマスプレゼントは\u0026hellip;?\nねこ湯たんぽでした。\nそとちゃんがおるすばんのときは事故が怖くてこたつの電源を入れられないんだけど、\n湯たんぽならこたつよりも安全に暖が取れると思い購入。\n開けてみるとこんな感じ。\nお店で見たときはそとちゃんくらいの大きさかな？と思ってたんだけど、\n実際はかなり大きかった\u0026hellip;\n気に入ってくれるかわからないけど、\nとりあえず温めてこたつに入れてみた。\n楽勝でした。\nこの湯たんぽが結構暖かくて、\nこたつの中がほかほかになるのでそとちゃん的にもいい感じらしい。\nなによりもねこが2匹くっついてるみたいでかわいい。\nよかったね〜\nおわり 今年もそとちゃんは元気でいい子だった。\n大きなアクシデントもなく、\n毎日遊んで食べて寝て楽しそうだったので本当によかった。\nシニアねこになって流石にちょっと落ち着いてくるかと思ったけど、\n全然そんなことはなく元気に騒いで遊んで何よりだった。\nちょっとお昼寝の時間が長くなったかな？というくらい。\n来年もそとちゃんが変わらず楽しく過ごせるよう、俺も頑張りたい。\nおまけ ","date":"2023-12-31T00:00:00Z","image":"/post/2023-12-31-sotochan/sotochan.jpeg","permalink":"/post/2023-12-31-sotochan/","title":"12月のそとちゃん(2023)"},{"content":"まとめ Azureのリソースグループ名に日本語(英数字以外の文字)を使用するとAzure内部での一部API呼び出し時にエラーが発生する ASCIIで表記できる文字(英数字と簡単な記号だけ)を使おう 経緯 Azure Data Factoryを試したくてリソースを作成していたときのこと。\nAzure Data Factory 用の Azure Private Linkを参考に、\n同時にプライベートエンドポイントを作成しようとした。\n各項目の設定はほぼデフォルトで、問題なくデプロイが開始した\u0026hellip;と思ったら、\nData Factory自体のデプロイはできたもののプライベートエンドポイントの作成がコケてしまった。\nなにいってだこいつ\u0026hellip;\n俺は愚かなお猿さんなので、\nこれを一時的なエラーと決めつけて脳死で再試行を10回程度繰り返したが、\n一度も成功することはなかった\u0026hellip;\n〜fin〜\n原因調査 俺は人類の誇りを取り戻し、\nおとなしくエラーの内容を読むことにした。\n表示されたエラーはこんな感じ。\n{ \u0026#34;code\u0026#34;:\u0026#34;DeploymentFailed\u0026#34;, \u0026#34;target\u0026#34;:\u0026#34;/subscriptions/xxxxxxxxxxxxxxxxxxxxx/resourceGroups/ふがふが/providers/Microsoft.Resources/deployments/Microsoft.DataFactory-20231212010148\u0026#34;, \u0026#34;message\u0026#34;:\u0026#34;At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/arm-deployment-operations for usage details.\u0026#34;, \u0026#34;details\u0026#34;:[ { \u0026#34;code\u0026#34;:\u0026#34;ResourceDeploymentFailure\u0026#34;, \u0026#34;target\u0026#34;:\u0026#34;/subscriptions/xxxxxxxxxxxxxxxxxxxxx/resourceGroups/ふがふが/providers/Microsoft.Resources/deployments/deployPrivateEndpoint-fugafuga-df-01-private-endpoint\u0026#34;, \u0026#34;message\u0026#34;:\u0026#34;The resource write operation failed to complete successfully, because it reached terminal provisioning state \u0026#39;Failed\u0026#39;.\u0026#34; } ] } Call to Microsoft.DataFactory/factories failed. Error message: The request URI \u0026lsquo;https://management.azure.com:443/subscriptions/xxxxxxxxxxxxxxxxxxxxx/resourcegroups/%25E3%2581%25B5%25E3%2581%258C%25E3%2581%25B5%25E3%2581%258C/providers/Microsoft.DataFactory/factories/fugafuga-df-01/privateEndpointConnectionProxies/fugafuga-df-01-private-endpoint.f596276c-4285-425c-9019-b5553ce6006e/operationstatuses/da7d1374-a224-428c-a13e-81c969ac4fa6?api-version=2018-06-01' is not valid, because it contains double encoding sequence \u0026lsquo;%25\u0026rsquo;. (コード: InvalidDoubleEncodedRequestUri)\n重要そうなのは2つ目のエラー文のInvalidDoubleEncodedRequestUriの部分。\nなんかデプロイ時に内部で叩かれてそうなAPIのURLに%25が含まれているのが問題と言われている(気がする)。\nここで怒られているURLをJSONのエラーメッセージに含まれるURLと比較すると、\n%25E3%2581%25B5%25E3%2581%258C%25E3%2581%25B5%25E3%2581%258Cとふがふがの部分が対応してそうというか、\nふがふががURLエンコードされたものがそれっぽい。\n実際に試してみると、ふがふがを2回エンコードしたものがまさに一致した。\n# jqでURLエンコード $ echo \u0026#39;ふがふが\u0026#39; | jq -Rr @uri %E3%81%B5%E3%81%8C%E3%81%B5%E3%81%8C # 2回エンコードしたら\u0026#39;%25\u0026#39;を含むエラーメッセージのURLと同じものが出てきた $ echo \u0026#39;%E3%81%B5%E3%81%8C%E3%81%B5%E3%81%8C\u0026#39; | jq -Rr @uri %25E3%2581%25B5%25E3%2581%258C%25E3%2581%25B5%25E3%2581%258C エラーの名前的にもこいつが原因と見て間違いなさそう。\n(エラー名もInvalidDoubleEncodedRequestUri:リクエストURIの不正な二重エンコード(意訳)だし)\nじゃあこのふがふがはどこからきているのかというと、\nリソース作成時に指定するリソースグループの名前だった。\nつまり\u0026hellip;\nリソースグループ名にマルチバイト文字を使用していると、\nそれがAPIのURLにエンコードされた時にエラーが発生する\n\u0026hellip;ってコト!?\n修正 ならリソースグループ名がURLエンコードが発生しない文字、\nすなわち半角英数ならいいんじゃね？\nできちゃったねぇ\u0026hellip;\nというわけで、\nAzureのリソースグループ名に全角文字を使うのはやめようと心に誓った。\n他のケース 上記の例はかなりわかりやすかったが、\n他のところでも全角文字が原因でData Factoryがうまく動かないことがあった。\n(というか、本当のところはプライベートエンドポイントではなくこっちでめちゃめちゃ時間消費してこの問題に気づいた)\n問題が起きたのはAzure Data Factory または Azure Synapse Analytics を使用して Azure Blob Storage のデータをコピーし変換するなどを参考にAzure Blob Storage上のExcelデータをData Factoryで整形するフローを作っていた時のこと。\nAzure Blob Storage上のExcelをデータセットとして参照できる状態で、\nそれをデータフローのソースとして参照すると接続時にエラーが発生してしまった。\nRequest headers must contain only ASCII characters. - RunId: 69672633-6312-4832-95fc-90a3d2b54678\nなにいってだこいつ\u0026hellip;\n他にエラーログが出ていないのでこのメッセージしかヒントにならないが、\nどうやら文字コードで怒られている様子。\n一瞬Excelのデータに全角文字列があるから\u0026hellip;? と思ったが、\n.xlsx形式で保存するときは文字コード選べないし、\nそもそもデータセットとして.xlsxが参照できるようになっている時点でデータ中身の文字コードが原因とは考えづらい。\n(実際.xlsx内の文字列を英数のみに替えたり、UTF-8で.csvとして出力したファイルに変えても変わらなかった)\nリクエストヘッダがどうとか言ってるし、\nこれも内部で叩こうとしてるアクセス先(Azure Blob Storage)のURLにASCIIで表現できない全角文字のリソースグループ名(ふがふが)が入ってるからでは\u0026hellip;?\nもしこの読みが正しいなら、\nリソースグループ名を英数字に変えたらうまくいくんじゃね\u0026hellip;?\nできちゃったねえ\u0026hellip;\nお気持ち 実際のところリソースグループ名に使える文字に制限はあるのだろうか。\n公式ドキュメントを漁って調べてみた。\nAzure リソースの名前付け規則と制限事項によると、\n一応リソースグループ名に使用できる文字は\nアンダースコア、ハイフン、ピリオド、括弧と、Char.IsLetterOrDigit関数で定義されている文字または数字。\nとなっているので、おそらくちゃんと読まずに日本語でふざけたリソースグループ名をつけた俺が100%悪い。\n\u0026hellip;が、ちょっともやもやするのはリソースグループ作成時に普通にマルチバイト文字が指定できちゃうこと。\nリソースグループ名の入力時と作成前の検証ステップで何も怒ってくれないのはどうなんだろう\u0026hellip;?\nバリデーションは機能しているのだろうか\u0026hellip;?\nおわり Azure初心者すぎてこんなところでコケてそこそこの時間を無駄にしてしまったので、\n戒めとして書いてみた。\nもちろんちゃんとルールを把握せず雰囲気で触った俺が100%悪いんだけど、\nシステムを壊すような操作はそもそもバリデーション等でユーザーがいじれないようにするべきだと思っているのでやっぱりもやもやする。\nAzureとは仲良くなれなさそうだな\u0026hellip;\nおまけ ","date":"2023-12-12T00:00:00Z","image":"/post/2023-12-12-do-not-use-japanese-for-azure-resource-group-names/sotochan.jpeg","permalink":"/post/2023-12-12-do-not-use-japanese-for-azure-resource-group-names/","title":"Azureのリソースグループ名に日本語を使うのはやめよう"},{"content":"まとめ こたつ 我が家もついにこたつを解禁！\n10月中から寒さ(?)を訴えてきたそとちゃん。\n11月中旬から急に冷え込んできたこともあり、\n念願かなって今年のこたつを手に入れた。\n解禁直後のそとちゃんの様子↓\nよほど嬉しかったのか、\nぷーぷー言いながら潜っていくのがかわいい。\nふとんを内側に寄せて、その上に座るのがやっぱり好きらしい。\n人間が先に入っていると、\nこたつ布団の上から膝に乗ってくるのがかわいい。\nよかったね〜\nふとん そとちゃんがこたつを手に入れた裏で、\n人間用のふとんも冬用のものに変えていた。\nニトリの毛布にもなる掛け布団カバーを買ってみたのだが、\nなんとこれが一瞬でそとちゃんのものになってしまった。\n肌触りが気に入ったのかな？\n昼間はよくこの上でひなたぼっこしている。\nこたつを手に入れて満足したはずのそとちゃんだったが、\n気づけばこたつよりもこの布団の上にいる方が多くなってしまった。\nやけど防止で最近はこたつの電源を入れずにエアコンで部屋を温めるようにしているので、\n下はNウォームの発熱、上はエアコンの温風で十分暖かいのかもしれない。\nとりあえずねこが幸せならそれでOKです。\nいもむし 冬用ふとんに替えてからというもの、\nたまに謎のいもむしが部屋に出現するようになった。\n体の表面に手脚のようなものは見当たらず、\n特に動く様子はない。\n近づいてよくみてみると体表は毛皮で覆われており、\n撫でてみるとゴロゴロ、プープーといった音を出す。\nいもむしが発生するタイミングは完全にランダムだが、\n俺が朝起きると腹の上にくっついていることがある。\n結構暖かくてきもちいい。\nまれにいもむしの別個体のような丸い毛玉が出現することもある。\nこの毛玉はいもむしよりもさらに動きが少ないが、\nお腹のような部分が一定のリズムで膨らんだりしぼんだりしていて、\n肺呼吸に近い動きをしていることがよくわかる。\n特に害があるわけでもないので今のところ駆除はせずに撫でているが、\n一体何者なんだ\u0026hellip;?\nおわり 10月は暖かすぎてついに日本も熱帯地域になったのか\u0026hellip;と思っていたが、\n11月中旬からしっかり寒気がやってきた。\nそとちゃんは暖かい部屋でぬくぬく過ごしている。\n野良猫時代は冬の時期も屋外で生活していたことを考えると、\nその生活の過酷さを想像すると共に、今後は一生この生活をさせてあげたいと再認識する。\nでも、ちょっと野生の誇りみたいなのを忘れてるんじゃないかな\u0026hellip;?\nおまけ ","date":"2023-12-03T00:00:00Z","image":"/post/2023-12-03-sotochan/sotochan.jpeg","permalink":"/post/2023-12-03-sotochan/","title":"11月のそとちゃん(2023)"},{"content":"まとめ ブログのお引越しをした。\n(old) https://uzimihsr.github.io/ → (new) https://blog.uzimihsr.com/\nGitHub Pagesでホスティングしていたこのブログのサイズが1GBを超えて更新できなくなってしまった Firebase Hostingにブログを移行した GitHub Actions上でのHugoビルドとFirebase Hostingへの自動デプロイを設定した Cloudflare Registrarで取得したドメインをFirebase Hostingで使えるようにした 移行元のGitHub Pagesから移行先のFirebase HostingにJavaScriptでリダイレクトさせた 経緯 薄々気づいてはいたが、Hugo+GitHub Pagesで作ってるこのブログ(https://uzimihsr.github.io/)の合計サイズがかなり大きくなっていて、\nGitHub Pagesのビルドにめっちゃ時間がかかったり、たまにコケたりするようになった。\nUploaded artifact size of 1111429120 bytes exceeds the allowed size of 1 GB. Deployment might fail.\nGitHub Pagesの利用上の制限のページにもGitHub Pagesサイトのサイズは1GBが上限とちゃんと書いてある。\n(読んでなかったか、読んだことを忘れていた。ごめんなさい\u0026hellip;)\nかといってこれまで書き殴ってきた記事を消したりするのもあまり嬉しくないので、\nおとなしく別の場所にお引越しする。\nやること Webサイトのお引越し、あまりやったことがないのでほぼ手探りだが以下のような作業を想定している。\n移行先の選定 どこにお引越しするか決める 移行先にページを作成 実際にお引越し先にページを置いてみる (やる気があれば)移行先でのCI/CD設定 お引越し先でも自動でページがデプロイされるようにする (やる気があれば)移行先のドメイン設定 自分の好きなドメイン名でアクセスできるようにする 移行元(GitHub Pages)から移行先へのリダイレクト設定 元のページを辿ってきた人が迷わないようにする 移行先の選定 まずは移行先を決める。\n令和の時代、Webサイトのホスティングサービスは星の数ほど存在する。\n\u0026ldquo;Webホスティングサービス 比較\u0026quot;とかで検索すれば候補がいくらでも出てくるが、\n自分の要件とコストに合ったものを選ぶべき\u0026hellip;\nなのだが、今回は脳死でGoogle Cloudに乗っかることにする。\n理由は単純で、\n規模がデカくて当分サ終しなさそうだから 個人的な事情(ひみつ)によりGoogle Cloudのクレジットが溜まっているから という感じ。\nこんなテキトーでいいのかな？いいよ(個人の趣味だし)\nただ、Google Cloudでのホスティング方法にも色々ある。\n一応クラウドをお仕事にしてきたエンジニアなので、\n練習としてCompute EngineやCloud Runでサーバ(コンテナ)を立ててデプロイや運用を頑張るべきなのでは\u0026hellip;とも思ったが、\n静的サイトなのにそんなことするの無駄に感じちゃったのと、\n何より運用を頑張りたくないからFirebase Hostingを採用する。\n料金を見た感じ10GBまでなら無料枠で使えそうなのもGood。\n移行先にページを作成 Hugo公式でHugo製のページをFirebase Hostingに載せる手順が紹介されているのでそれに従う。\nまずはブログ用のFirebaseプロジェクトを新規に作成。\nUIから作る方法は世の中にいっぱい転がってそうなので敢えてCLIで作ってみる。\n# Firebase CLIの更新 $ npm install -g firebase-tools # CLIバージョン確認 $ firebase --version 12.9.1 # ログイン $ firebase login --reauth プロジェクト作成。\n全世界で一意なプロジェクトIDを先に指定しなきゃいけないのが若干面倒。\n# 適当なIDでプロジェクトを作成 $ PROJECT_ID=(project ID) $ firebase projects:create $PROJECT_ID ? What would you like to call your project? (defaults to your project ID) blog ✔ Creating Google Cloud Platform project ✔ Adding Firebase resources to Google Cloud Platform project 🎉🎉🎉 Your Firebase project is ready! 🎉🎉🎉 Project information: - Project ID: (project ID) - Project Name: blog Firebase console is available at https://console.firebase.google.com/project/(project ID)/overview 世の中に同じプロジェクトID(他の人が先に作ったやつ)がある場合はこんな感じで怒られるので、\nどうにかして一意なやつを考える必要がある。\n$ firebase projects:create $PROJECT_ID ? What would you like to call your project? (defaults to your project ID) ✖ Creating Google Cloud Platform project Error: Failed to create project because there is already a project with ID (project ID). Please try again with a unique project ID. Having trouble? Try again or contact support with contents of firebase-debug.log 次にHugoプロジェクトのディレクトリでFirebaseプロジェクトを初期化する。\nFirebase featuresはHosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploysを選択。\nFirebaseプロジェクトは先ほど作成したものを選択。\nHugoでビルドしたページのHTMLとかはpublicディレクトリに出力されているのでそれをそのまま使う。\nCI/CDの設定は一旦スキップし、\nページを上書きする系の質問はなんとなくNoとした。\n$ firebase init ######## #### ######## ######## ######## ### ###### ######## ## ## ## ## ## ## ## ## ## ## ## ###### ## ######## ###### ######## ######### ###### ###### ## ## ## ## ## ## ## ## ## ## ## ## #### ## ## ######## ######## ## ## ###### ######## You\u0026#39;\u0026#39;re about to initialize a Firebase project in this directory: /path/to/blog ? Which Firebase features do you want to set up for this directory? Press Space to select features, then Enter to confirm your choices. Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys === Project Setup First, let\u0026#39;\u0026#39;s associate this project directory with a Firebase project. You can create multiple project aliases by running firebase use --add, but for now we\u0026#39;\u0026#39;ll just set up a default project. ? Please select an option: Use an existing project ? Select a default Firebase project for this directory: (project ID) (blog) i Using project (project ID) (blog) === Hosting Setup Your public directory is the folder (relative to your project directory) that will contain Hosting assets to be uploaded with firebase deploy. If you have a build process for your assets, use your build\u0026#39;\u0026#39;s output directory. ? What do you want to use as your public directory? public ? Configure as a single-page app (rewrite all urls to /index.html)? No ? Set up automatic builds and deploys with GitHub? No ? File public/404.html already exists. Overwrite? No i Skipping write of public/404.html ? File public/index.html already exists. Overwrite? No i Skipping write of public/index.html i Writing configuration info to firebase.json... i Writing project information to .firebaserc... ✔ Firebase initialization complete! そうするとこんなファイルが生成されている。\n.firebaserc\n{ \u0026#34;projects\u0026#34;: { \u0026#34;default\u0026#34;: \u0026#34;(project ID)\u0026#34; } } firebase.json\n{ \u0026#34;hosting\u0026#34;: { \u0026#34;public\u0026#34;: \u0026#34;public\u0026#34;, \u0026#34;ignore\u0026#34;: [ \u0026#34;firebase.json\u0026#34;, \u0026#34;**/.*\u0026#34;, \u0026#34;**/node_modules/**\u0026#34; ] } } 本当は文法とかちゃんと勉強するべきだろうけど、\nなんとなく.firebasercでFirebaseプロジェクトのIDを指定していて、\nfirebase.jsonでFirebase featureの種類と公開用ディレクトリの場所、\nホスティングしないファイルのパターンが指定されてそう。\n早速Firebaseへのデプロイを試してみる。\n$ hugo \u0026amp;\u0026amp; firebase deploy Start building sites … hugo v0.98.0+extended darwin/arm64 BuildDate=unknown | EN -------------------+------- Pages | 236 Paginator pages | 123 Non-page files | 753 Static files | 0 Processed images | 1982 Aliases | 44 Sitemaps | 1 Cleaned | 0 Total in 5024 ms === Deploying to \u0026#39;(project ID)\u0026#39;... i deploying hosting i hosting[(project ID)]: beginning deploy... i hosting[(project ID)]: found 5998 files in public ✔ hosting[(project ID)]: file upload complete i hosting[(project ID)]: finalizing version... ✔ hosting[(project ID)]: version finalized i hosting[(project ID)]: releasing new version... ✔ hosting[(project ID)]: release complete ✔ Deploy complete! Project Console: https://console.firebase.google.com/project/(project ID)/overview Hosting URL: https://(project ID).web.app 指定されたHosting URLを開くとページが閲覧できるようになっている。\nやった〜。\nCI/CDの設定 ページの手動デプロイができたので、\nさらに自動で簡単にデプロイできるようCI/CDを設定する。\nfirebase initを再度実行し、今度はHosting: Set up GitHub Action deploysを選択する。\n途中でGitHubへのログインを求められるが、\nこれに応えることでFirebaseへのリリース権限を持つTokenがGitHub ActionsのSecrets(FIREBASE_SERVICE_ACCOUNT_(project ID))に自動登録される。\n$ firebase init ######## #### ######## ######## ######## ### ###### ######## ## ## ## ## ## ## ## ## ## ## ## ###### ## ######## ###### ######## ######### ###### ###### ## ## ## ## ## ## ## ## ## ## ## ## #### ## ## ######## ######## ## ## ###### ######## You\u0026#39;\u0026#39;re about to initialize a Firebase project in this directory: /path/to/uzimihsr/blog Before we get started, keep in mind: * You are initializing within an existing Firebase project directory ? Which Firebase features do you want to set up for this directory? Press Space to select features, then Enter to confirm your choices. Hosting: Set up GitHub Action deploys === Project Setup First, let\u0026#39;\u0026#39;s associate this project directory with a Firebase project. You can create multiple project aliases by running firebase use --add, but for now we\u0026#39;\u0026#39;ll just set up a default project. i Using project (project ID) (blog) === Hosting:github Setup i Detected a .git folder at /path/to/uzimihsr/blog i Authorizing with GitHub to upload your service account to a GitHub repository\u0026#39;\u0026#39;s secrets store. Visit this URL on this device to log in: https://github.com/login/oauth/authorize?client_id=... Waiting for authentication... ✔ Success! Logged into GitHub as uzimihsr ? For which GitHub repository would you like to set up a GitHub workflow? (format: user/repository) uzimihsr/blog ✔ Created service account github-action-201048321 with Firebase Hosting admin permissions. ✔ Uploaded service account JSON to GitHub as secret FIREBASE_SERVICE_ACCOUNT_(project ID). i You can manage your secrets at https://github.com/uzimihsr/blog/settings/secrets. ? Set up the workflow to run a build script before every deploy? No ✔ Created workflow file /path/to/uzimihsr/blog/.github/workflows/firebase-hosting-pull-request.yml ? Set up automatic deployment to your site\u0026#39;\u0026#39;s live channel when a PR is merged? Yes ? What is the name of the GitHub branch associated with your site\u0026#39;\u0026#39;s live channel? master ✔ Created workflow file /path/to/uzimihsr/blog/.github/workflows/firebase-hosting-merge.yml i Action required: Visit this URL to revoke authorization for the Firebase CLI GitHub OAuth App: https://github.com/settings/connections/applications/... i Action required: Push any new workflow file(s) to your repo i Writing configuration info to firebase.json... i Writing project information to .firebaserc... ✔ Firebase initialization complete! 指示にしたがって最後まで進めると今度は次のようなファイルが生成される。\n.github/workflows/firebase-hosting-merge.yml\n# This file was auto-generated by the Firebase CLI # https://github.com/firebase/firebase-tools name: Deploy to Firebase Hosting on merge \u0026#39;on\u0026#39;: push: branches: - master jobs: build_and_deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: FirebaseExtended/action-hosting-deploy@v0 with: repoToken: \u0026#39;${{ secrets.GITHUB_TOKEN }}\u0026#39; firebaseServiceAccount: \u0026#39;${{ secrets.FIREBASE_SERVICE_ACCOUNT_(project ID) }}\u0026#39; channelId: live projectId: (project ID) .github/workflows/firebase-hosting-pull-request.yml\n# This file was auto-generated by the Firebase CLI # https://github.com/firebase/firebase-tools name: Deploy to Firebase Hosting on PR \u0026#39;on\u0026#39;: pull_request jobs: build_and_preview: if: \u0026#39;${{ github.event.pull_request.head.repo.full_name == github.repository }}\u0026#39; runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: FirebaseExtended/action-hosting-deploy@v0 with: repoToken: \u0026#39;${{ secrets.GITHUB_TOKEN }}\u0026#39; firebaseServiceAccount: \u0026#39;${{ secrets.FIREBASE_SERVICE_ACCOUNT_(project ID) }}\u0026#39; projectId: (project ID) これらの変更をcommitしてmasterブランチに取り込む。\n$ git add . $ git commit -m \u0026#34;Firebase\u0026#34; $ git push origin master masterブランチにcommitが取り込まれると、早速.github/workflows/firebase-hosting-merge.ymlで定義されたGitHub Actionsが実行される。\nこれだけだとちょっとわかりづらいが、\nFirebase側で確認すると確かにGitHub Actionsからリリースが走ったことが確認できる。\nが、ここで問題が\u0026hellip;\nさっきまで見えていたFirebaseのページがPage Not Foundになってしまった。\n元々このディレクトリではpublicディレクトリの内容をGitHub Pages用のリポジトリ(uzimihsr/uzimihsr.github.io)に反映させるためにsubmodule化しており、\nおそらくGitHub Actions上ではこのディレクトリのファイルがファイルとして参照できてない?ような気がする。\n(ローカルでfirebase deployしたときはpublic内のファイルが参照できていたのでリリース後にページが見られた)\nactions/checkoutのREADMEを確認したところ、\nsubmodulesというパラメータを指定することでsubmodule先のリポジトリが参照できるらしい。\n.github/workflows/firebase-hosting-merge.ymlを次のように変更、masterにpushして再挑戦。\nname: Deploy to Firebase Hosting on merge \u0026#34;on\u0026#34;: push: branches: - master jobs: build_and_deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # v3→v4に変更 with: submodules: recursive # submodulesを参照するためのパラメータ - uses: FirebaseExtended/action-hosting-deploy@v0 with: repoToken: \u0026#34;${{ secrets.GITHUB_TOKEN }}\u0026#34; firebaseServiceAccount: \u0026#34;${{ secrets.FIREBASE_SERVICE_ACCOUNT_(project ID) }}\u0026#34; channelId: live projectId: (project ID) 今度はActionsがコケてしまった。\nrepository \u0026lsquo;https://github.com/uzimihsr/uzimihsr.github.io.git/' not found clone of \u0026lsquo;git@github.com:uzimihsr/uzimihsr.github.io.git\u0026rsquo; into submodule path \u0026lsquo;/home/runner/work/blog/blog/public\u0026rsquo; failed\nエラーメッセージ的に対象のリポジトリが参照できずエラーになっているっぽい。\nここで思い出したのだが、対象のリポジトリ(uzimihsr/uzimihsr.github.io)はPrivateリポジトリだった。\nこういうときどうすんだろう\u0026hellip;と思い再度actions/checkoutのREADMEを確認するとちゃんと答えがあった。\ncheckout対象のPrivateリポジトリに関する権限を持つトークンをtokenパラメータで指定すればいいらしい。\n本当は真面目にGitHub App作るべきな気もするが、\n今回はサクッとFine-grained PATで対象Repoに対するRead権限を持つトークンを作成する。\nこのトークンをGitHub Actionsを実行するリポジトリのSecretsに追加。\n.github/workflows/firebase-hosting-merge.ymlにこのSecretを参照する記述を追加して再挑戦。\nname: Deploy to Firebase Hosting on merge \u0026#34;on\u0026#34;: push: branches: - master jobs: build_and_deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: recursive token: \u0026#34;${{ secrets.PRIVATE_REPO_READ_TOKEN }}\u0026#34; # Secretsに追加したFine-grained PAT - uses: FirebaseExtended/action-hosting-deploy@v0 with: repoToken: \u0026#34;${{ secrets.GITHUB_TOKEN }}\u0026#34; firebaseServiceAccount: \u0026#34;${{ secrets.FIREBASE_SERVICE_ACCOUNT_(project ID) }}\u0026#34; channelId: live projectId: (project ID) 今度はActionsが通った。\nFirebase上のページも404が解消されてコンテンツが表示できている。\nやった〜。\n\u0026hellip;と思ったけど、ここで気になったことが1点。\nビルドしたページを格納するpublicディレクトリをgit repoで管理し続ける必要ってあるんだろうか\u0026hellip;?\nHugo公式のCI/CD設定手順でも\nDon’t forget to update your static pages before push!\nと紹介されているように、\nこの手順だとmarkdownの更新とhugoによるページのビルド(publicディレクトリの更新)は変わらずローカルPCで実施する必要がある。\nどうせGitHub Actionsを使うなら、\nActions上でhugoを動かしてpublicディレクトリを更新するようにして、\nローカルでの作業はmarkdownの更新とgit pushだけにできないだろうか\u0026hellip;?\n(要はローカルPCからhugoを抜いても困らないようにしたい)\nGitHub Actions上でHugoを動かす方法についてちょっと調べるとpeaceiris/actions-hugo@v2というものが出てきた。\n(Hugo公式のGitHub Pages対応ページでもGitHub Actions上でHugoを動かす例はあるんだけど、今回はこのActionsを利用することにする)\nREADMEにしたがって.github/workflows/firebase-hosting-merge.ymlに追記してみる。\nname: Deploy to Firebase Hosting on merge \u0026#34;on\u0026#34;: push: branches: - master jobs: build_and_deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: recursive token: \u0026#34;${{ secrets.PRIVATE_REPO_READ_TOKEN }}\u0026#34; - uses: peaceiris/actions-hugo@v2 # Actions上でのHugoセットアップ with: hugo-version: \u0026#34;0.119.0\u0026#34; extended: true - run: hugo --minify # Hugoビルドの実施 - uses: FirebaseExtended/action-hosting-deploy@v0 with: repoToken: \u0026#34;${{ secrets.GITHUB_TOKEN }}\u0026#34; firebaseServiceAccount: \u0026#34;${{ secrets.FIREBASE_SERVICE_ACCOUNT_(project ID) }}\u0026#34; channelId: live projectId: (project ID) publicディレクトリ(submodule)が残ってると邪魔そうなのでついでに消す。\n$ git submodule e328611c605affe6d4672527704f3f5c730f5289 public (heads/master) 907f881f5d773d9d94a470fa5643fcaade904555 themes/hugo-theme-stack (v1.1.0-282-g907f881) $ git submodule deinit -f public Cleared directory \u0026#39;public\u0026#39; Submodule \u0026#39;public\u0026#39; (git@github.com:uzimihsr/uzimihsr.github.io.git) unregistered for path \u0026#39;public\u0026#39; $ git submodule -e328611c605affe6d4672527704f3f5c730f5289 public 907f881f5d773d9d94a470fa5643fcaade904555 themes/hugo-theme-stack (v1.1.0-282-g907f881) $ git rm public rm \u0026#39;public\u0026#39; $ rm -rf .git/modules/public $ git submodule 907f881f5d773d9d94a470fa5643fcaade904555 themes/hugo-theme-stack (v1.1.0-282-g907f881) この状態でmaster pushしてみるとどうなるか\u0026hellip;?\nやった〜。\n最終的なGitHub Actionsの設定ファイルはこんな感じ。\n結局Privateリポジトリへの参照が要らなくなったのでPAT(Secrets)は削除した。\n.github/workflows/firebase-hosting-merge.yml\nname: Deploy to Firebase Hosting on merge \u0026#34;on\u0026#34;: push: branches: - master jobs: build_and_deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: recursive - uses: peaceiris/actions-hugo@v2 with: hugo-version: \u0026#34;0.119.0\u0026#34; extended: true - run: hugo --minify - uses: FirebaseExtended/action-hosting-deploy@v0 with: repoToken: \u0026#34;${{ secrets.GITHUB_TOKEN }}\u0026#34; firebaseServiceAccount: \u0026#34;${{ secrets.FIREBASE_SERVICE_ACCOUNT_(project ID) }}\u0026#34; channelId: live projectId: (project ID) .github/workflows/firebase-hosting-pull-request.yml\nname: Deploy to Firebase Hosting on PR \u0026#34;on\u0026#34;: pull_request jobs: build_and_preview: if: \u0026#34;${{ github.event.pull_request.head.repo.full_name == github.repository }}\u0026#34; runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: recursive - uses: peaceiris/actions-hugo@v2 with: hugo-version: \u0026#34;0.119.0\u0026#34; extended: true - run: hugo -D --minify - uses: FirebaseExtended/action-hosting-deploy@v0 with: repoToken: \u0026#34;${{ secrets.GITHUB_TOKEN }}\u0026#34; firebaseServiceAccount: \u0026#34;${{ secrets.FIREBASE_SERVICE_ACCOUNT_(project ID) }}\u0026#34; projectId: (project ID) ドメインの設定 Firebaseで公開したページはデフォルトで無料のドメイン(project ID).web.appが付与されている。\nこれがプロジェクト作成時にidをユニークにしたために結構ダサい名前になってしまったので、\nいい感じのドメインに変えてあげたい。\n(HTTPS化されてればドメインはあんまり気にしないつもりだったけど、idが想像以上にダサかったので\u0026hellip;)\nドメイン取得はGoogle Domainsでやりたかったが、サ終しているので他を探す。\nいろいろ探した結果、Cloudflare Registrarが一番安くてUIもしっかりしているので採用。\nuzimihsr.comを取得した。\n(手順のメモを忘れたけど、クレカの登録ができれば後はとにかく簡単だった。費用は1年で$9.77くらい。)\nFirebase公式のカスタム ドメインを接続するを参考に進めてみる。\nまずはFirebase Hosting側で先ほど取得したドメインを追加する。\nこの状態で続行するとDNSに登録すべきレコードが表示される。\nこの表示されているレコードをCloudflare側でDNSに追加する。\nTXTレコードの設定をしてから反映されるまでにちょっと時間がかかる。\n30分くらい経つとドメインの準備が完了し、\nホスティングへの誘導で追加のレコードが表示されるのでこれを再度DNS側で追加する。\nここからの待ち時間が結構長い。\n最大24時間かかると言われたが、それ以上待っても状況が変化しなかった。\n泣きながら色々調べたらこんなのを発見。\nhttps://community.cloudflare.com/t/flexible-ssl-redirect-loop-with-google-firebase/2063/3\nhttps://developers.cloudflare.com/ssl/troubleshooting/too-many-redirects/#flexible-encryption-mode\nどうやらオリジン(今回はFirebase Hosting)側でhttp→httpsへのリダイレクト設定がある状態でCloudflare側のSSL/TLS暗号化モードがフレキシブル(デフォルト)だとリダイレクトループが発生するらしい。\n解決するにはこの暗号化モードをフルに変更すれば良いとのこと。\nするとあっさりホスティングへの誘導が完了し、対象のドメイン名でページが表示できるようになった。\n0.00001%くらいの確率で今度ブログ以外にもページを作りたくなるかも\u0026hellip;と思ったので、\nブログ専用のサブドメイン(blog.uzimihsr.com)なども同様の手順で追加した。\nhttps://blog.uzimihsr.com/\n最終的な設定はこんな感じ。\n移行元から移行先へのリダイレクト設定 ブログの移行ができたので、いよいよ移行元のGitHub Pagesが不要になった。\n本当はいきなり爆破できれば楽なんだけど、\n割といろんなところに記事のリンクを貼って(貼られて)しまっているので404になるのは好ましくない。\nというわけで移行元のURLでアクセスされたときに移行先の同じページにリダイレクトするようなページを作成して、\n既存のGitHub Pagesを差し替えるようにしたい。\n(例) https://uzimihsr.github.io/post/2023-11-11-handmade-pc/ → https://blog.uzimihsr.com/post/2023-11-11-handmade-pc/\nちょっとググってみたところGitHub Pagesでは.htaccessが使えず301リダイレクトができないっぽいので、\n脳死でJavaScriptを使ったリダイレクトを試してみる。\n参考: https://www.w3docs.com/snippets/javascript/how-to-redirect-a-web-page-with-javascript.html\nindex.html\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;en\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Moved!\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1\u0026gt;Moved!\u0026lt;/h1\u0026gt; \u0026lt;p\u0026gt;You will be redirected in 5 seconds...\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;If you are not redirected, \u0026lt;a href=\u0026#34;https://blog.uzimihsr.com\u0026#34;\u0026gt;click here\u0026lt;/a\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;script\u0026gt; const newHost = \u0026#34;https://blog.uzimihsr.com\u0026#34;; setTimeout(() =\u0026gt; { window.location.href = newHost; }, 5000); \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 404.html\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;en\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Moved!\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1\u0026gt;Moved!\u0026lt;/h1\u0026gt; \u0026lt;p id=\u0026#34;newUrl\u0026#34;\u0026gt;\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;You will be redirected in 5 seconds...\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;If you are not redirected, \u0026lt;a href=\u0026#34;https://blog.uzimihsr.com\u0026#34;\u0026gt;click here\u0026lt;/a\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;script\u0026gt; const newHost = \u0026#34;https://blog.uzimihsr.com\u0026#34;; const redirectUrl = window.location.href.replace( window.location.origin, newHost ); document.getElementById(\u0026#34;newUrl\u0026#34;).innerHTML = \u0026#34;new URL: \u0026#34; + redirectUrl; setTimeout(() =\u0026gt; { window.location.href = redirectUrl; }, 5000); \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; あとはGitHub Pagesにこの2枚のHTMLだけを残し、それ以外のファイルを全削除。\n$ pwd /path/to/uzimihsr/uzimihsr.github.io $ ls -a1 . .. .git 404.html index.html $ git add . $ git commit -m \u0026#34;bye-bye GitHub Pages\u0026#34; $ git push origin master 元のhttps://uzimihsr.github.io/を開くとindex.htmlが開かれて こんな感じになる。\n5秒放置するとhttps://blog.uzimihsr.com/にジャンプ(リダイレクト)する。\n(JavaScriptが無効だと動かないけど)\nhttps://uzimihsr.github.io/post/2023-11-11-handmade-pc/みたいにホスト名以降のパスが指定されている場合、404.htmlによってhttps://blog.uzimihsr.com/post/2023-11-11-handmade-pc/にリダイレクトする。\nこれで移行元から移行先へのリダイレクト設定もできた。\nブログのお引越し作業は以上で終わり。\nおつかれさまでした\u0026hellip;\nおわり 手探りでめちゃめちゃ時間はかかったが、\nなんとかブログのお引越しができた(はず)。\nたいへんだった〜。\nFirebase Hostingならページのサイズは10Gまで無料だし、\nそれを超えても課金すれば継続して利用できるはずなので\nきっと二度とやることはないでしょう\u0026hellip;🚩\nおまけ ","date":"2023-11-29T00:00:00Z","image":"/post/2023-11-29-moving-from-github-pages-to-firebase-hosting/sotochan.jpeg","permalink":"/post/2023-11-29-moving-from-github-pages-to-firebase-hosting/","title":"このブログをGithub PagesからFirebase Hostingに移行する"},{"content":"まとめ 初めて(ちゃんとした)自作PCを組んでみた。\nその過程で自作初心者の自分が考えたこととかのメモ。\n種類 名前+型番 税込価格(円) CPU Intel® Core™ i5-13500 34,980 マザーボード ASUS TUF GAMING B660-PLUS WIFI D4 (CPUとセット売り) 20,000 グラフィックボード MSI GeForce RTX™ 4070 VENTUS 2X 12G OC 85,800 CPUクーラー DeepCool AK400 R-AK400-BKNNMN-G-1 3,480 メモリ Crucial 64GB Kit (2 x 32GB) DDR4-3200 UDIMM CT2K32G4DFD832A 16,500 SSD Western Digital WD_BLACK SN850X NVMe™ SSD WDS100T2X0E 12,980 電源 玄人志向 KRPW-GA750W/90+ 13,480 ケース ADATA XPG VALOR AIR JP2 VALORAIR-WHJ2 7,480 OS Windows 11 Home 17,980 合計 212,680 BTOの方が安く済んだとか言っちゃいけない。\nこれは趣味です。\n性能については後で記述するが、\nホグワーツ・レガシーが解像度UWQHD(3440×1440)の75fpsでそこそこ快適に遊べている。\n自分の理解度 組み始める前の自作PCに関する知識はこんな感じ(だった)。\nガッチガチのPC初心者というわけではないと思う。\n各パーツの役割やソフトウェア周り(BIOS/UEFI, OS)はなんとなく理解している 組み立ての手順はわかっていない だいぶ昔にBTOを利用したことはあるけど、\n自力で組んだことはないしパーツの選び方とかもよくわかっていなかった。\n目的/予算 自作経験者に「自作PC組みたいんだよね〜」と聞くと必ず返ってくるのが\n「目的と予算は？」だと思う。\n今回はこんな感じ。\n目的 家にMacしかないのでWindows機が欲しい Windows on MacOSの構成はあまりやりたくなかった\u0026hellip; ホグワーツ・レガシーをストレスなく遊びたい ついでに動画編集もできるとうれしい 予算 20万円前後 それっぽい理由を並べているが、\n実際のところはただ単に自作という行為に憧れがあったというのが大きい。\n構成 組み終わった今振り返ると、\n自作の工程で一番大変で一番楽しいのがパーツ選びだったように思う。\nとりあえず初心者がいろいろ考えたところでろくな結果にはならないと思い、\n今回は金だけ持って脳死でツクモに行ってきた。\nなのでほぼ店員さんの受け売りにはなってしまうが、\nパーツ選びのときに考えたことを将来の自分のために書いておく。\n(店員さん忙しそうなのにめちゃ優しかったです、ありがとうございました)\n※情報は購入時(2023年9~10月ごろ)のもの\nCPU Intel® Core™ i5-13500\nIntelにした理由\nお店に行くまではAMD(Ryzen)の方がIntel(Core)よりお手頃価格だと思っていたのだが、\n実際には最新のRyzen7000シリーズだとメモリの規格がDDR5に縛られてしまい、\nマザーボードとメモリを合わせたトータルだと13世代Intel Coreシリーズの方が若干安くなる計算になった。\n13世代はDDR4,5両対応だし、将来DDR5で組みたいとなった時にも使えるはず。\n(その頃にはCPUも新しいのが欲しくなってそうだが\u0026hellip;)\n13世代Core i5にした理由\n性能について強いこだわりはなく、\n13世代Coreシリーズであればi5で十分、\ni7との差額をグラボの費用にあてるべきと判断した。\ni7以上だと冷却も水冷/簡易水冷を検討しなきゃいけなくて値段が嵩張るというのもある。\n12世代にしてもっと安くできたかもしれないが、\n自分が購入した時期だと12世代の在庫(選択の幅)が少なかった。\n内蔵GPU有りにした理由\n内蔵GPU非搭載のモデル(近いやつだとi5-13400F)と若干迷ったが、\nグラボがぶっ壊れたときのトラブルシュートを考えて内蔵GPU有りのモデルにした。\nあと内蔵GPUの有無でそこまで値段が変わらなかった。\n本当の理由\nマザーボードとセット売りされてて安かった。\nマザーボード ASUS TUF GAMING B660-PLUS WIFI D4\nATXにした理由\nデカいと組みやすい。\n組み終わった後で考えてみると、確かにパーツ同士の干渉とかを気にせず組めた気がする。\n(Micro-ATXとかの規格で組んだことないから正確なところはわからんが\u0026hellip;)\nDDR4にした理由\nCPU選びの際にも触れたが、安いから。\n自分が選んだタイミングではDDR5対応のマザーボードが軒並みお高めだった。\nWi-fiモデルにした理由\nルーターとの距離とかもそんなにないので有線LANで良いのだが、\nBluetoothを楽に使いたかった。\n組んだ後もネットワークは有線、デバイスはBluetoothで繋いでいる。\n本当の理由\nCPUとセット売りされてて安かった。\nあと元々は12世代Coreシリーズ向けのモデルだが、\nお店でBIOSアップデート済みだったので余計な作業が発生せず助かった。\n(13世代CPUとセット売りだから当然と言われたらそれはそう)\nグラフィックボード MSI GeForce RTX™ 4070 VENTUS 2X 12G OC\nNVIDIAにした理由\nなんとなく馴染みのあるメーカーだから。\nAMD Radeonも一瞬考えたけど、\nそこまで安くなかったのとCPUがAMDじゃないので相性とかも別にいいかな\u0026hellip;となった。\nRTX 4070にした理由\n最初はVRAMの容量重視で4060Ti(VRAM 16GB)を考えていたんだけど、\n自分が使うモニターのスペック(UWQHD/75Hz)だとVRAMよりもコア数の方がカギになるとのことでちょっとお金を積んで4070にした。\nあとは電力効率が良く冷却のことをそこまで考えなくて良さそうだったのもポイント。\nMSIにした理由\n一番安かった。\n本当の理由\nRTX 4080以上のハイエンドだと流石に予算的にキツかったし、\n万が一壊したときのことを想像したくもなかった。\nあとCPUのボトルネックガーとか言われると泣きそうだった。\nCPUクーラー DeepCool AK400 R-AK400-BKNNMN-G-1\n空冷にした理由\n水冷はメンテが面倒\u0026hellip;と思っていたらメンテが簡単な簡易水冷というのもあるらしい。\nが、簡易水冷も冷却水が補充できないとかで寿命があったり、\n価格もお高めということで初心者も安心な空冷に落ち着いた。\nCPU付属のクーラーにしなかった理由\n今回のCPUにはトップフロー型のファンが付属でついていたが、\n性能が低いとのことで諦めた。\nこのモデルにした理由\n光ってないから。\n虹色は個人的に好きじゃないのと、\n下手に光ってねこちゃんにいたずらされるのが怖かった。\nあと安かった。\nこの価格帯の中では性能が良くてかなり人気らしい。\n組み立てのことを考えるとグリス塗布済みなので自分で塗る必要がなくてうれしい。\n取り付け方の動画が公式で提供されてたのも助かった。\nメモリ Crucial 64GB Kit (2 x 32GB) DDR4-3200 UDIMM CT2K32G4DFD832A\nDDR4にした理由\nCPU選びの際にも触れたが、やっぱり安いから。\n64GBだと特にDDR5より安かった。\n64GBにした理由\nメモリサイズでマウントをとりたかったから\n趣味で長めの動画や4K動画を書き出すことがあるので、\n快適に作業できるようにしたかった。\nこのモデルにした理由\nDDR4, 64GBの中では一番コスパが良さそうだった。\nあと光ってないから(重要)\nSSD Western Digital WD_BLACK SN850X NVMe™ SSD WDS100T2X0E\n1TBにした理由\n2TBでも良いかと思ったんだけど、値段を考慮して1TBにした。\nもし足りなくなってもメモリよりは追加しやすいはずだし\u0026hellip;\nこのモデルにした理由\n読み出し速度が爆速だから。\nゲーム用途を考えると速ければ速いほどいいらしい。\nあと光ってないから。\n(最近はSSDも光るらしい。気を抜くとPCパーツはすぐ光る)\n電源 玄人志向 KRPW-GA750W/90+\n750Wにした理由\nグラボとCPUの消費電力(200W+65W)を考慮するとだいたい23倍の650750Wくらいが良いと考えていた。\nこのモデルにした理由\n入門用の価格帯で一番パフォーマンスが良いとされるものを選んだ。\nあとケーブルがプラグイン方式なのも組み立ての際に配線が楽だった。\nケース ADATA XPG VALOR AIR JP2 VALORAIR-WHJ2\n選んだ理由\nとにかく安かった。\nケースファンが元々3(前面)+1(背面)台ついていて、\nかつファンコントロール機能があるものの中では一番安かった。\nとにかく見た目を地味にしたかったので本当は側面が透明でないものがよかったが、\n今そんなモデルはほとんどないらしく実際にお店にも在庫がなかった。\n組み立て ねこに手伝ってもらいながらなんとか組み立てられた。\nパーツの取り付けは割とカンタンだった。\n決められたソケットに端子をはめ込んでいくだけなのでプラモデル感覚でできた。\n動画には残せてないが一番手間取ったのが配線。\n電源ケーブルをマザーボードのどこに刺すかでめちゃくちゃ迷った。\n最初の電源投入時は絶対失敗するとか画面に何も映らないとか聞いてたのでめちゃめちゃビビってたが、\n今回は運良く一発で起動できたので嬉しかった。\n最終的にパーツの取り付けからOSインストールまでは合計で4,5時間くらいかかった。\n休日を消しとばすにはいい趣味になりそう。\n性能 動くものが組めたので性能を見てみる。\nCINEBENCH 自作PCを組んだらまずはベンチマークを回すものらしいのでCINEBENCH 2024を試してみた。\nスコアはこんな感じ。\nType Score(pts) GPU 16598 CPU(Multi Core) 1051 CPU(Single Core) 98 正直スコアの見方がよくわかっていないが、\n概ね他サイト等で公表されている値より10%低い程度なのでまあこんなものか\u0026hellip;? 誤差\u0026hellip;? 何かが足を引っ張ってるのかもだけど、冷却の度合いとか色々条件があるんだろうな〜。\n極端に低いわけじゃないので、初期不良とか取り付けミスの線はあまりなさそうでひとまず安心。\n以下、ベンチマークの参考にさせていただいたサイト\nhttps://www.cgdirector.com/cinebench-2024-scores/\nhttps://pcrecommend.com/cpu/cinebench2024/\nホグワーツ・レガシー そして自作PCを組んだ大きな目的だった、ホグワーツ・レガシー起動中の状況はこんな感じ。\nMSI Afterburnerで左上にCPU,GPUの状態を表示させている。\nゲーム側の設定は画質が最高品質、\nフレームレートは75fps(ディスプレイのリフレッシュレートが75Hzのため)、\nレイトレーシングOFF、\nその他はたぶんいじってない。\n解像度はUWQHD(3440×1440)なんだけど綺麗に表示できていて、\nフレームレートも常時75fpsに張り付いている。\nレイトレーシングをONにするとたまに重く感じることもあるが、\n素人の自分には正直ON/OFFの違いがわからなかったのでOFFにして遊んでいる。\n(まだ光が反射するような場面で遊んでないからかも\u0026hellip;)\nあとはVRAMの使用量が10GBくらいほどあり、\n12GBのVRAMで一応耐えられているか\u0026hellip;?という感じ。\nCPU, GPUの使用率は落ち着いているので多分楽しく遊べているんじゃないかな\u0026hellip;?\n一応推奨スペックのULTRA仕様くらいは満たせてそうだし\u0026hellip;\nおわり 新しい趣味が増えるのはたのしい。\nほぼ初めての経験でちょっと不安だったが、意外と動くものが組めてよかった。\n相談に乗ってくれたおともだちのみんなとツクモの優しい店員さんに感謝でございます。🙏\nまたお金と時間に余裕ができたら今度はRyzen+Radeonで小型のものも組んでみたい。\nパーツ選びで見るべきポイントとかもなんとなくわかったので、\n暇な時にふらっとPC専門店に行ってパーツを眺めるのも楽しそうだな〜。\nおまけ ","date":"2023-11-11T00:00:00Z","image":"/post/2023-11-11-handmade-pc/sotochan.jpeg","permalink":"/post/2023-11-11-handmade-pc/","title":"自作PCを組んだ"},{"content":"まとめ まるい 最近はそとちゃんがまるまっていることが多かった。\nなんかおとなしいな〜と思ったらだいたいバリバリボウルで丸くなっている。\n(ひなたぼっことか遊んでるときはそとちゃんがちょこちょこ喋るので音でわかる)\nさほど暖かくないボウル(段ボール製)の上でまるまるのは何か意図があるのだろうか\u0026hellip;?\n「私はこんなに寒い思いをしています」\nとアピールしてはやくこたつを出させようとする作戦かもしれない。\nでもこたつ出すとそとちゃん本当に動かなくなっちゃうし\u0026hellip;\n右目の調子 8月くらいから怪しかった右目の目ヤニだが、\nだんだんと良くなってきた感じがする。\nもちろん完全に出なくなることはないんだけど、\nひどい時に比べるとドロっとした目ヤニは少なくなった。\n目ヤニが少なければウェットシートで簡単に拭き取れるのでこちらも助かるし、\nそとちゃんも嫌なお手入れが短く済んでうれしそう。\nいいね〜\nよく遊ぶ 元々よく遊ぶそとちゃん。\n薄明薄暮性だからか、\n特に朝と夕方は元気に走り回っている。\n朝は7~8時ごろにエンジンがかかり出す。\n(もしかしたらもっと早く起きてるかも?その時間は俺が寝てるからわからない)\n遊ぶところを見ていてほしいのか、\n一人遊びできるおもちゃで遊ぶ前にも必ず俺に向かって鳴いてくる。\nかわいい。\n夕方も17~18時くらいになると急に爆鳴きして走り回る。\n朝に比べると、\n夕方は一緒に遊ぶ系のおもちゃ(猫じゃらしとかねずみのおもちゃ)を期待していることが多い。\nそれ以外におもちゃに強いこだわりはなく、\n目の前にあるものでなんでも遊んでくれる。\nたくさん遊んでえらい！\nハロウィン 今年のハロウィンもそとちゃんに仮装してもらった。\nやっぱりマントは嫌がらずに着てくれてかわいい。\n次はクリスマスかな\u0026hellip;?\nおわり 9月に救急にお世話になったのを忘れてしまうくらい、\n10月のそとちゃんは元気だった。\n今年は10月でもまだまだ暖かい日が続いていて、\n体温調節がぶっ壊れるかと心配したがねこは平気な様子。\nそとちゃんははやくこたつに入りたがっているようだが、\n当分の間は暖房なしでも暮らせちゃいそう\u0026hellip;\n果たして11月はこたつの出番があるのだろうか\u0026hellip;?\nおまけ ","date":"2023-11-05T00:00:00Z","image":"/post/2023-11-05-sotochan/sotochan.jpeg","permalink":"/post/2023-11-05-sotochan/","title":"10月のそとちゃん(2023)"},{"content":"まとめ 病院 年1回のワクチン接種で病院へ。\n毎度のことながらキャリーには自分から飛び込んでいく。\nいくらやる気があっても車移動がやっぱり苦手で、\n道中はずっと鳴いていた。\n病院についたころにはお疲れの様子。\n診察室に入ると最初はビビって出てこなかったが、\nキャリーから出したあとは落ち着いていた。\n体重は4.1kg、体温は37.5℃。\n触診と心音も問題なしだった。\n前回と違って、\nおしりで体温を測るときにあまり暴れずえらかった。\n先生が薬を取りに行っている間にまた探検。\n元々注射は得意なので、ワクチンは暴れずにあっさり接種完了。\nたくさん探検して落ち着けたのも良かったかもしれない。\n帰りの車内でもわんわん鳴いて、\n家に着いたらすぐお昼寝。\nおつかれさまでした。\nソファ そとちゃんはソファの主である。\nそもそも家の主がそとちゃんだろうと言われればそれはそう\u0026hellip;\nなんだけど、最近は特にお気に入り。\n爪とぎ防止のためにかけてた布を6月くらいから違うものに変えたのだが、\nその触感が気に入ったっぽい？\n気がつけばだいたいソファに座っている。\n主なので当然なのだがそとちゃんはソファの真ん中に座る🤔\n元々2人がけの小さめソファなので、\nそとちゃんが真ん中に居ると人間が座る余地はない。\n俺が床に直座りしてても譲る気はないらしい😭\n気にいってくれたなら何よりです\u0026hellip;\n病院その2 月末のある日のこと。\n晩ごはんを食べた後いつものようにそとちゃんをお腹の上に載せてまったりしていたら、\n体勢を変えようとしたそとちゃんがバランスを崩してベッドに落ちてしまった。\n嫌な予感がした。\nいつもならベッドに落ちそうになると爪を立ててふんばる(痛い)はず。\n抱っこしてみるといつもより力が抜けていてぐったりしていて、\nやっぱり様子がおかしい。\n意識ははっきりしていて呼びかけにも応えるんだけど、\n普段より元気が無くて一度座るとそのまま全然動かない。\n試しに歩行を促しても数歩歩いたところですぐ座り込んでしまう。\n骨折や脱臼を疑い手足をさすってみても痛がるような反応はなかった。\nこれはまずいと思い、深夜だったが病院に電話。\n幸いすぐに診てくれるとのことだったので車で病院へ。\n車中ではいつも以上にそとちゃんが大騒ぎ。\nキャリーの中で爆鳴きしたかと思ったらすぐに吐いてしまった。\n病院に到着してすぐに診察。\n(事前に受け入れの準備をしてもらえていて非常にありがたかった)\n車中ではよく確認できなかったが嘔吐物は茶色で、\n晩ごはんの猫缶を吐いたような感じだった。\n診察でまず聞かれたのは誤飲の可能性。\n過去に一度やらかしているがそれ以降一度も変なものを飲みこんだことはなく、\n紐状や口に入るサイズのおもちゃの管理は気をつけていたはず。\n直近でおもちゃが消えたこともなかったのでこれはなさそう。\n先生によると呼吸が荒く、\n肺の音に乱れがあるので急いでレントゲンとエコーを撮ることになった。\n待合室で検査を待つこと30分。\n先生に「状態が良くないかもしれない」と言われたのを頭の中で繰り返してしまい、\n正気ではいられなかった。\n\u0026hellip;診察の結果。\nエコーやレントゲンの結果はとても綺麗で、 内臓や骨に大きな問題はなかった。\n強いて言えば血中酸素濃度が少し低く、胃と膵臓に若干炎症があるかも？というくらいだが、\n急性膵炎の場合はもっと高頻度で嘔吐するので違うだろうという診断。\n可能性を考えられるのは急性胃腸炎だが、\n1度吐いた後すっかり元気なので、なんとなくただ気持ち悪かっただけかもしれないとのこと😭\n(呼吸や肺の音が荒かったのは吐いた直後だったからっぽい)\n精密検査は不要になったが、\n念のため急性胃腸炎の対症療法を行うことに。\n吐き気止めと消化器促進のお薬としてセレニアとプリンペランの注射、\nそれと嘔吐で失った水分を補う皮下注射(点滴のようなもの)を打ってもらった。\n処置後も元気にしていたので入院せずそのまま帰宅。\n注射をしたのでご飯を無理に食べさせる必要はないが(食べられるなら食べてもOK)、\n再度吐いたり呼吸が荒くなったらまた連れてくるようにとのことだった。\n帰宅後はそれまでが嘘だったかのように元気に部屋の中をうろうろ。\nなんとなくごはんを欲しがる素ぶりをしていたので試しにちゅーるをちらつかせたらあっさり食いつき、\nものすごいスピードで完食。\nその後はいつも通り元気に夜鳴き\u0026amp;運動会をしてから就寝。\n翌朝も何事もなかったかのように寝ていた。\nそれ以降も吐いたり様子がおかしくなることはなく、\nいつもの元気に叫んで走り回るそとちゃんになった。\nよかった\u0026hellip;\n(お財布には大ダメージ💸)\nおわり 9月は2回も病院に行くことになってしまい、\nしもべとしては大変だったが本人(ねこ)は至って元気だった。\n夜間救急にお世話になった件ではお金は飛んだが、\n様子が怪しいと思った時にすぐに行ける病院があることと、\n深夜でも躊躇なく病院に行く判断ができたのは良かったと思う。\nお金は飛んだが(重要)。\nここからだんだん寒くなってくるはずだが、\n病院にかかることがないくらい元気に過ごしてもらいたい。\nおまけ ","date":"2023-10-13T00:00:00Z","image":"/post/2023-10-13-sotochan/sotochan.jpeg","permalink":"/post/2023-10-13-sotochan/","title":"9月のそとちゃん(2023)"},{"content":"まとめ ハンモック 窓に取り付けてあるねこ用ハンモック。\n8月はここがお気に入りだった。\n毎朝必ずここにいて、どうやら夜もハンモックで寝ている様子。\n寝てない時は外で飛んでる鳥を観察したり、\nベランダの洗濯物を監視してくれている。\nたすかる(?)\n(ちなみにクラッキングはしない。シャーと同様にやり方がわかってなさそう😹)\n窓のカーテンが閉まっているとそとちゃん1人ではハンモックに登れないので、\n開けろ開けろとわんわん鳴いたりする。\nかわいいね〜\n電動ねこじゃらし だいぶ前に買った電動ねこじゃらし。\nすぐ電池が切れちゃうのであんまり使っていなかった。\n最近充電式電池を買ったので、\n久しぶりに出してみたらねこ大喜び。\n飽きずによく遊んでいる。\nたまに興奮しすぎて(ねこが)どっかに飛んでいくのが意味わかんなくておもしろい。\n安全のために何分か経ったら勝手に止まるようになってるんだけど、\nねこは遊び足りなくてプレッシャーをかけてくる。\nたのしそうだし運動不足も解消できていいね〜\n涙やけ問題 8月の後半あたりから右目の目ヤニというか涙やけがひどくなってきた。\n毎年梅雨の時期にこの症状が出ていて、\n今年は珍しく落ち着いていると思ったが結局ちょっと遅れて来てしまった。😿\nこれ(猫風邪の後遺症)は保護されたときからのことで、\n病院でも治すのは難しいと言われているが\u0026hellip;\nやっぱりみてるとこっちがつらくなってくる。\n例年涼しくなると症状がマシになるので、\n早くおさまってほしい。\nおわり 8月も元気だったそとちゃん。\n右目はかわいそうだけど、\n本人(ねこ)はあんまり気にしてないし毎日元気に食べて遊んで寝ている。\n今年の夏は暑すぎてあまりできていなかったが、\nまたキャリーおさんぽにも行きたいので(というかねこが行きたがってそう)早く涼しくなってほしい。\nおまけ ","date":"2023-09-09T00:00:00Z","image":"/post/2023-09-09-sotochan/sotochan.jpeg","permalink":"/post/2023-09-09-sotochan/","title":"8月のそとちゃん(2023)"},{"content":"まとめ ひなたぼっこ 夏。\nあつすぎる。\nなのにねこは毎日窓際でひなたぼっこ。\n暑くないのだろうか。\n最近は日の出も早いので、\n欠かさず朝からスタンバイ。\n天気が悪いと不機嫌になるくらいにはお日様が好き。\n日に当たりすぎるのも良くないと思い時々抱っこして移動させるが、\n目を離すと勝手に窓際に戻ってしまう。\nねこは常に自分の気持ちよさ優先で、\nたとえ暑いと感じていても日差しが気持ちいいとやめられないとかなんとか。\nちょっとアホなのがかわいい。\n常にエアコンガンガンで部屋はそこまで暑くないし、\nそとちゃんが楽しいならいいか\u0026hellip;\n袋 何十回目かの袋ブーム到来。\nニトリさんのでかいレジ袋にクタクタになった紙袋を入れただけのいつものやつ。\nいつか捨てようとおもって放置していたらまた気に入られてしまった。\n最近はこの袋がもうビリビリに破けていて、\nただ上から乗るだけの寝床になっている。\nいい加減新しい袋に替えたいけど、\n捨てたら怒りそうだな\u0026hellip;\nおわり ただただ書くのが遅い。\nそとちゃんは相変わらず元気。\nむしろ俺の方が暑さにやられてしまっているかもしれない。\nおまけ ","date":"2023-08-17T00:00:00Z","image":"/post/2023-08-17-sotochan/sotochan.jpeg","permalink":"/post/2023-08-17-sotochan/","title":"7月のそとちゃん(2023)"},{"content":"まとめ 草 一瞬の気の迷いでうっかり猫草を買ってしまった。\n(前科があるのに\u0026hellip;)\nやっぱりそとちゃんは草が大好き🌱\nというか草ジャンキー。😵‍💫\n大喜びでカッ喰らう。\nそして吐く🤮\n(汚いので写真なし)\nあまりにも勢いよく食べては吐くのを繰り返してしまうので、\nごほうびとしてたまにお風呂場であげることにした。\n右の奥歯を抜いてしまったので、\nうまく噛みきれずどうしても顔が左に傾いちゃうのがかわいそうだけどちょっとかわいい。\n草が室内にあるとそとちゃんが食べすぎてしまうので基本はベランダに置いていたが、\nそとちゃんはあまりにも草が好きすぎて窓に張り付く始末。\n日中はずーっと草を眺め続ける生活になってしまった。\nまた、お風呂場で草をあげるのが習慣化していたので\n「お風呂場に行く→草が出てくる」と覚えてしまった様子。\n脱衣所のドアを閉めて入れないようにすると今度はその下から無理やり手を突っ込んできた。\n草への執着心がもはや中毒者のそれである。\nそんな草ライフを送っていたそとちゃんに悲劇。\n連日の猛暑に耐えきれず、\n草が日焼けして枯れてしまった😢\n吐きすぎるとそとちゃんの健康にもよくないので、\nそのまま処分して代わりの草も買わないことにした。\nまた俺の気が狂った時に買おう\u0026hellip;\nカーテン 草を見張る目的で窓際にいることが多かったそとちゃんがカーテン遊びにハマってしまった。\n元々カーテンの生地が好きみたいでよく上に乗っていたのだが、\n最近は薄い布がひらひらするのが面白いようでよく遊んでいる。\nもぐったりしばいたり(?)、遊び方は多彩。\nカーテンが丈夫な素材でそとちゃんの爪では破けないのが幸い。\n思う存分遊んでストレス解消してくれればそれでいいです。\nソファ そとちゃんのストレス解消法は他にもある。\nそれはソファでの爪研ぎ😭\n以前から肘掛けのところをよく破壊していたのだが、\n流石にやりすぎて手応えがなくなったのか最近は座るところの側面がターゲットになっている。\nひっくり返って仰向けになり、\nバリバリしながら移動していく。\nまるで天井に張り付いたクモのような洗練された動き🕷️ (動画を撮れなかったのが残念)\nうちはのびのび教育がモットーなので\nそとちゃんに危険がない限りは何やっても怒らないんだけど、\nそれでも糸くずが出たりクッションの中身が出るのが怖いので対策することにした。\n破壊された部分に雑に当て布をして補強🪡\nさらに座面に毛布をかけてバリバリしづらくした。\n当て布にちょっと硬めの素材を選んだので、\nそとちゃんがバリバリしてもなかなか破けない👍\nなお、背もたれ側\u0026hellip;😭\nおわり 自分が最近妙に忙しく、\n書くのがめっちゃ遅くなってしまった。\n最近は猛暑日が続くけどそとちゃんは元気(エアコンガンガンだし)。\n毎日叫んで走り回っている。\nこのまま夏バテ知らずで乗り切りたい。\nおまけ ","date":"2023-07-17T00:00:00Z","image":"/post/2023-07-17-sotochan/sotochan.jpeg","permalink":"/post/2023-07-17-sotochan/","title":"6月のそとちゃん(2023)"},{"content":"まとめ 誕生日 元野良なので誕生日がわからないそとちゃん。\n一応5月生まれということにしていて、今年でたぶん7さいになった。\n今年の誕生日プレゼントはでかいキャットタワー。\n(たぶん)誕生日プレゼント pic.twitter.com/gYZXuybmcA\n\u0026mdash; ずみし (@uzimihsr) May 1, 2023 組み立て中から興味津々。\n今回買ったやつは天井と床に突っ張るタイプで、\n本当に天井ギリギリくらいまでの高さがある。\n最大の特徴はこの透明なボウル。\nここに入ってるところを下からみたらめっちゃかわいいんだろうな\u0026hellip;と思って買ったが、\nそとちゃんは透明な部分が穴に見えたみたいでなかなか入ってくれない。\n慣れてもらうためにお気に入りのおもちゃをボウルに入れてみた。\n浮いてるように見えるのが不思議みたいで興味を持ちはじめるそとちゃん。\nそこからは割とあっさりで、\nおもちゃでちょこちょこ遊んでるな〜と思ってたら数分後にはすっぽり入っていた。\nたすかる〜〜😊\nおまちかねのローアングルはこんな感じ。\nご自慢のピンク肉球がぺったりついてて超かわいい\u0026hellip;🥰\n誕生日ごはんは例年ケーキを買ってアホみたいに残されるので、\n今年はシンプルにいつものおやつとちゅーるだけでお祝いした。\n今年も余裕の完食。\nごちそう pic.twitter.com/NjpaUe1vd8\n\u0026mdash; ずみし (@uzimihsr) May 1, 2023 今年も無事に誕生日が迎えられてよかった。\nちなみに今まで使ってたキャットタワーもいまだに現役。\nというか、1週くらいしたら結局元のタワーに登る方が多くなってしまって、\n新しいやつでは全然遊ばなくなってしまった\u0026hellip;\nしっぽです pic.twitter.com/zdi0FkQ7AY\n\u0026mdash; ずみし (@uzimihsr) May 29, 2023 一瞬でも楽しんでくれたならOKです。\n記念写真 ここ数年は毎年写真屋さんで誕生日の記念写真を撮ってもらっている。\n今年も同じところで撮ってもらった。\n被写体 pic.twitter.com/6q4AFayOG7\n\u0026mdash; ずみし (@uzimihsr) May 21, 2023 そとちゃんはやっぱりスタジオで大興奮。\n隅から隅まで探検する。\n気が済むまでうろうろしてからいざ撮影。\n今年はそとちゃんが協力的だったのかカメラマンさんが上手なのか、\n結構スムーズに撮ることができた。\nあとはおもちゃを大量に持参してカメラマンさんの後ろで振りまくったのがよかったのかもしれない。😂\n今年は集中力が切れることもなく、 そとちゃん単体でもたっぷり撮ってもらうことができた。\n(去年は落ち着きがなさすぎて俺が無理やり抱っこして撮ったものが多かった)\n今年もかわいい写真をたくさん撮ってもらえてよかった。\nおわり そとちゃんが無事に7さいを迎えられた。\n推定3さいのころにお迎えしたので、もう丸4年一緒に暮らしていることになる。\nそとちゃんが保護されるまでよりも俺と暮らしている時間の方が長くなった(推定)というのはなんかうれしい。\n8さいも健康に迎えられるように頑張ろう\u0026hellip;\nおまけ ","date":"2023-06-06T00:00:00Z","image":"/post/2023-06-06-sotochan/sotochan.jpg","permalink":"/post/2023-06-06-sotochan/","title":"5月のそとちゃん(2023)"},{"content":"まとめ 手術 ついにスケーリング手術を行うことになったそとちゃん。\n全身麻酔の手術は2回目なので、そとちゃんも俺も慣れたもの。\n当日の体調もよく、キャリーにも自分から入り意気揚々と病院へ。\nそれでも診察室に入ると急にぐずりだす。\n昔はあんなにはしゃいでたのに\u0026hellip;\nなんとか(無理やり)引っ張り出し、\n当日の検査も体重4.1kg, 体温37.8℃でほぼ問題なし。\n全身麻酔と手術内容についての説明をしっかり受け、\nそとちゃんを預けた。\n俺はそのまま前回同様近所の神社で神頼み。\n術前術後の電話をもらい(手術自体はかなり早く終わった)、\n麻酔が切れたあとの経過観察が終わってから夕方にお迎え。\nここで悲しいお知らせ。\n一番症状がひどかった右上の奥歯だが、\nぐらつきがひどかったため抜歯になってしまった。\nこれが術前の状態。\n奥歯が黒くなっているのと、他の歯も根本が若干黄色くなっている。\n術後がこんな感じ。\n抜歯したほか、黄色く歯石がついていた部分を綺麗にしてもらった。\n術後の説明も上記画像などを混ぜながら丁寧にしてもらった。\n口腔内にウイルス性の症状は見受けられなかった(単純に歯石がついていただけ)とのことで、\n追加の通院などは不要になった。\n術後のそとちゃんはこの表情。\n超しょんぼりしている。\n前回の手術のときもそうだったけど、\n麻酔で寝てる間におしっこがちょっと漏れておしりが汚れるのがめちゃめちゃ嫌みたい。\nかわいそう\u0026hellip;\n帰宅後は疲労困憊かと思いきやごはんをよこせと大騒ぎ。\n先生から「術後は痛みもあるしお水だけで大丈夫ですよ\u0026hellip;」と言われていたが、\nこの日のそとちゃんは麻酔に備えて前日の夜10時から何も食べておらず腹ペコなのだった。\nすこしだけおやつを出したらすぐ完食。\nそんな感じで大変な一日が終了。\n翌日は心配なので丸一日付きっきりで様子を見ていたが、\n初めこそちょっとだるそうにしていたもののお昼頃にはいつも通り遊んで食べて寝ての生活に戻った。\nちょっと元気出てきた pic.twitter.com/oQPbuuYKAO\n\u0026mdash; ずみし (@uzimihsr) April 3, 2023 一応処方してもらった痛み止めの薬もちゃんと飲んだ。\nえらい。\n抜歯跡は出血もなく、綺麗に塞がった。\n1本だけ抜いたので噛み合わせが悪くなるのでは？と心配だったが、\nよくわかんないけどねこはそういうのあまり気にしない生き物らしい。\nくわ〜っ pic.twitter.com/mGdzi8pNCw\n\u0026mdash; ずみし (@uzimihsr) April 26, 2023 とりあえずお口まわりの問題が無事に終わって良かった。\nちなみに今回かかった費用はこんな感じ。\nお財布に大ダメージ\u0026hellip;\nこたつとさようなら そとちゃんにとって悲しい出来事がもう一つ。\nついにこたつを撤去した。\nこたつ なくなった pic.twitter.com/Bo4fdRMWY6\n\u0026mdash; ずみし (@uzimihsr) April 28, 2023 片付ける片付けると言い続けてもう4月。\n4月も中旬からかなり暖かくなってきたのでいよいよ片付けることにした。\nまずはこたつがなくても部屋が暖かいことを理解してもらうため、\nこっそり電源を落とすところから\u0026hellip;\nこたつの電源を落とすところから始めていく pic.twitter.com/v1huvgXKV7\n\u0026mdash; ずみし (@uzimihsr) April 13, 2023 が、ダメ。\n毎日こたつでゴロゴロしているそとちゃんにはすぐバレてしまった。\nそとちゃんは意志が強いので、\n電源が入るまでこたつの周りをうろうろにゃんにゃんする。\nこうなるとそとちゃんと俺で根比べになるのだが、\n集中力が続かない俺と違ってそとちゃんはおもちゃやおやつにも釣られず命懸けで抗議を続ける。\nそんなそとちゃんに勝てるはずもなく、\nこのときは片付けを断念。\n懸命の抗議活動で暖かいこたつを取り戻したねこ pic.twitter.com/REhyHn031i\n\u0026mdash; ずみし (@uzimihsr) April 19, 2023 しかしこの1週間後、転機が訪れる。\nたまたまタイミング良く(?)、そとちゃんがこたつ布団の上にGEROを吐いてしまったのだ。\nこれでは仕方ないので速攻で布団を外して洗濯に。\nそんなわけで長く続いたそとちゃんのこたつ生活は割とあっけなく終了したのだった。\nかなしそう pic.twitter.com/ONevBHblnK\n\u0026mdash; ずみし (@uzimihsr) April 28, 2023 今回の冬もたくさんお世話になったこたつ。\nまた半年後くらいに会いましょう\u0026hellip;\nこたつがなくても太陽がある pic.twitter.com/G9S7Zu8kKU\n\u0026mdash; ずみし (@uzimihsr) April 13, 2023 おわり 春は別れの季節なので(?)、4月はそとちゃんにとって悲しいイベントが多かった。\nかわいそう。\n歯の件は心配だったけど本人(ねこ)はあんまり気にしてない様子。\nせっかく綺麗にしてもらったのでこれを継続できるように歯みがきをがんばりたいところ。\nこたつは結構むりやり撤去してしまったが、\n2日も経たないうちに他の場所でごろごろするようになったのでこれもそんなに気にしてないと思いたい。\nというか4月下旬は流石にもう暑かったし\u0026hellip;\n5月でそとちゃんは(たぶん)7さいになる。\n一緒に暮らし始めてからもう4年になることにただただ驚き。\nおまけ ","date":"2023-05-05T00:00:00Z","image":"/post/2023-05-05-sotochan/sotochan.jpg","permalink":"/post/2023-05-05-sotochan/","title":"4月のそとちゃん(2023)"},{"content":"まとめ 表情 そとちゃんの表情はよくわからない。\nイカ耳になってるときのねこって不機嫌なはずなんだけど、\nそとちゃんはこんな感じで思いっきり耳を絞ってても喉をゴロゴロ鳴らしていたりする。\nこの顔で超ごきげんなのよくわからない pic.twitter.com/HVxjfpJN9k\n\u0026mdash; ずみし (@uzimihsr) March 24, 2023 この状態のときはなかなかひざから降りないので本当に機嫌がいいはず。\n逆に仰向けになってリラックスしてるようにみえるときでもめちゃめちゃ機嫌が悪いことがある。\n激おこねこちゃん pic.twitter.com/63boxsg8AP\n\u0026mdash; ずみし (@uzimihsr) March 17, 2023 このときは頭をなでようとしただけでも攻撃してくる。\nどうして\u0026hellip;\nこんな感じでそとちゃんのご機嫌は表情からではよくわからない。\n結局のところ喉を鳴らしているかどうかで判断している。\n\u0026hellip;ちなみに喉を鳴らしながら何故か攻撃してくることもあるので本当のところはなにもわかっていない。\nテレビっ子 去年の年末くらいに我が家にでかいテレビを導入した。\n#WorldBaseballClassic pic.twitter.com/XtlfABJaZE\n\u0026mdash; ずみし (@uzimihsr) March 21, 2023 それまで使っていた小さいテレビにはあまり興味がなさそうだったそとちゃんだが、\nさすがにこの大きさと明るさ(あと画質がチョーきれい)だと気になる様子。\n世界ネコ歩きを流しっぱなしにしてると結構集中して見ている。\n(前のテレビで流しても全然見なかった)\nちなみにテレビに向かって話しかけたり画面にちょっかい出したりはしないので、\n映っているねこが本物じゃないというか本当はそこにいないことは理解してるっぽい。\nかしこい。\n最近のテレビはYouTubeも見られるということで、\n試しにこんな感じの鳥やら小動物がぴょこぴょこ映る猫用動画を流したら大ハマり。\nこたつの上にどかっと座ってずーっと見ている。\nカウチポテト？\nYouTubeを観る現代っ子 pic.twitter.com/h5MNczcJJZ\n\u0026mdash; ずみし (@uzimihsr) March 26, 2023 仕事中にそとちゃんがめちゃ暴れてるときとかに流すと一瞬で大人しくなるので助かる。\n子供にYouTubeを観せて大人しくさせる親の気持ちがちょっとわかった気がする\u0026hellip;\n病院 だいぶ前から歯石が気になっていたそとちゃんだったが、\n3月の中旬から毎日の歯磨きを本当に痛がるようになってしまったため病院へ行った。\n体重は4.08kgでいい感じ、\n体温は37.6度でちょっと低めだった。\n診察してもらったところやはり歯肉炎が酷くなっていて、\nスケーリング(歯石除去)をすることになった。\nこの日は施術を行わず、\n全身麻酔の準備として血液検査だけやってもらった。\n結果は毛包嚢胞の手術のときと同様にグロブリン(免疫機能を示すやつ)が高めで、\nやはり猫風邪の後遺症があるのだろうという話。\nそれ以外の数値は問題なく健康だった。\nこの日は天気が良かったので、\n診察後は久しぶりのお散歩がてら歩いて家まで帰った。\n病院から家の途中にはいい感じの池がある公園があって、\nそとちゃんは結構満喫していた様子。\n10分くらいずーっと見ていた。\n結局1時間くらいお散歩して、家についたらごほうびタイム。\nものすごい勢いでちゅーるを貪る。\n全身麻酔の手術は不安だけど、\nとりあえずそとちゃんは元気だった。\nおわり かなりあったかくなってきて、そとちゃんは毎日いい感じ。\nよく遊んで食べて寝て元気に過ごしている。\nテレビばかりだと退屈しそうなのでそろそろお散歩を再開してもよさそう。\nちなみにこたつはまだ片付けられていない。\n4月こそは強い意志を持って片付けたい。\n(ネタバレ?になるけどこの日記を書いている時点でスケーリングの手術は成功しているが、いろいろあったので来月ちゃんと書く)\nおまけ ","date":"2023-04-06T00:00:00Z","image":"/post/2023-04-06-sotochan/sotochan.jpeg","permalink":"/post/2023-04-06-sotochan/","title":"3月のそとちゃん(2023)"},{"content":"まとめ お留守番 大変申し訳ないのだが2月はそとちゃんにお留守番してもらう機会がちょっと多かった。\nそとちゃんは自立した大人の女性(推定6歳)だし、そんなのは余裕\u0026hellip;\nではない。\nお留守番のときはこんな感じでめちゃめちゃ機嫌が悪くなる。\n普段俺が家にいてもごはんのときと遊ぶとき以外ほとんど寝てるのに、\n外出から戻ったタイミングでは高確率で起きて文句を言いにくる。\nごはんは自動給餌器で出している(しかもこの場合はちゃんと完食する)ので、\n空腹ではなくお留守番させられたことに腹を立てているのは確か。\nちなみにこのとき顔は怒っていて耳もイカ耳になるのだが、\nしっぽはピンと立って震えているので\nお留守番させられた怒りとしもべ(俺)が帰ってきた喜びが混ざり合った変な感情になっている。\nような気がする。\nとてもかわいい。\nまた、お留守番中はずっと寝ているわけではなく、\n結構元気に走り回っていることもある。\nたまに外出先からペットカメラで見ているので何をしてるかはだいたい筒抜けなのだが、\nそうとは知らないそとちゃんはこのとき結構悪さをする。\n普段登ると怒られる台所に乗ったり、ソファを破壊したりと割とやりたい放題。\n最初お迎えした頃(まだ毎日出社していた時期)はお留守番余裕ねこちゃんだったので、\nこの数年の在宅勤務で俺が常に家に居る状況に慣れてしまったのかもしれない。\n(お留守番中に吐いたりトイレの失敗はないのでひどい分離不安とかではないと思いたいが\u0026hellip;)\nこたつのプロ こたつの上に乗る楽しみを見つけたそとちゃんだったが、\n2月はどちらかというとこたつの中で過ごすことが多かったように思った。\nこたつ pic.twitter.com/e4N7cZFaWu\n\u0026mdash; ずみし (@uzimihsr) February 2, 2023 2月は部屋の乾燥が気になってエアコンを止めることが多かったりしたのでそのせいかもしれない。\n特に寒い日の朝なんかは気づいたらこたつの中にいた。\nこたつの中で寝るときにはこだわりがあって、\n入るときに頭で布団を押して内側に折り込んでその上に寝っ転がるのがけっこう好き。\nこたつ pic.twitter.com/YQ0umUlguz\n\u0026mdash; ずみし (@uzimihsr) February 8, 2023 もちろんそうじゃないときもある。\nねてる pic.twitter.com/oESUDYjISD\n\u0026mdash; ずみし (@uzimihsr) February 7, 2023 こたつの中で溶けてるときのそとちゃんがかわいくて、\nついつい写真を撮ってしまう。\nこたつ pic.twitter.com/MuhlU0mWTI\n\u0026mdash; ずみし (@uzimihsr) February 16, 2023 が、やりすぎると普通に嫌そうにする。\nむずかしい。\nおわり そとちゃんは2月もごろごろして元気だった。\nこれを書いている時点(3月中旬)でだいぶ暖かくなっているのでこたつはそろそろ片付けたいが、\n果たして無事にいくだろうか\u0026hellip;\nおまけ ","date":"2023-03-14T00:00:00Z","image":"/post/2023-03-14-sotochan/sotochan.jpeg","permalink":"/post/2023-03-14-sotochan/","title":"2月のそとちゃん(2023)"},{"content":"まとめ jq(yq)のwith_entries, select, testを組み合わせるとオブジェクトのキー名の正規表現でフィルタをかけることができる。\n# regexには正規表現のパターンを記述する jq \u0026#34;with_entries(select(.key|test(\\\u0026#34;${regex}\\\u0026#34;)))\u0026#34; yq \u0026#34;with_entries(select(.key|test(\\\u0026#34;${regex}\\\u0026#34;)))\u0026#34; 環境 jq 1.6 yq v4.30.8 キーに正規表現を当てる こんな感じのJSONまたはYAMLについて、.config配下のspec111とspec222の要素だけを正規表現で抜きたいとする。\nこんなときはwith_entriesとselect, testを使うと良い感じにできる。\n# 先頭がspecで始まり数字で終わるパターン $ regex=\u0026#39;^spec[0-9]+$\u0026#39; $ cat example.json | jq \u0026#34;.config | with_entries(select(.key|test(\\\u0026#34;${regex}\\\u0026#34;)))\u0026#34; { \u0026#34;spec111\u0026#34;: { \u0026#34;aaa\u0026#34;: 123, \u0026#34;bbb\u0026#34;: 456 }, \u0026#34;spec222\u0026#34;: { \u0026#34;aaa\u0026#34;: \u0026#34;abc\u0026#34;, \u0026#34;bbb\u0026#34;: \u0026#34;def\u0026#34; } } $ cat example.yaml | yq \u0026#34;.config | with_entries(select(.key|test(\\\u0026#34;${regex}\\\u0026#34;)))\u0026#34; spec111: aaa: 123 bbb: 456 spec222: aaa: abc bbb: def なにが起こっているのか jq Manualによるとwith_entries(foo)は\nto_entries | map(foo) | from_entriesと同じ意味なので、\n先ほどのコマンドを下記のように分解して何が起こっているのか詳細を追っていく。\n(yqについてもjqとほぼ同じはず)\n# 下記2行は同じ操作 jq \u0026#34;.config | with_entries(select(.key|test(\\\u0026#34;${regex}\\\u0026#34;)))\u0026#34; jq \u0026#34;.config | to_entries | map(select(.key|test(\\\u0026#34;${regex}\\\u0026#34;))) | from_entries\u0026#34; # yqについても同じ yq \u0026#34;.config | with_entries(select(.key|test(\\\u0026#34;${regex}\\\u0026#34;)))\u0026#34; yq \u0026#34;.config | to_entries | map(select(.key|test(\\\u0026#34;${regex}\\\u0026#34;))) | from_entries\u0026#34; まずはto_entriesから。\nこれを噛ませるとキーの文字列に.keyとしてアクセスできるようになる。\nhttps://stedolan.github.io/jq/manual/#to_entries,from_entries,with_entries\nhttps://mikefarah.gitbook.io/yq/operators/entries\n$ cat example.json | jq \u0026#34;.config | to_entries\u0026#34; [ { \u0026#34;key\u0026#34;: \u0026#34;spec111\u0026#34;, \u0026#34;value\u0026#34;: { \u0026#34;aaa\u0026#34;: 123, \u0026#34;bbb\u0026#34;: 456 } }, { \u0026#34;key\u0026#34;: \u0026#34;spec222\u0026#34;, \u0026#34;value\u0026#34;: { \u0026#34;aaa\u0026#34;: \u0026#34;abc\u0026#34;, \u0026#34;bbb\u0026#34;: \u0026#34;def\u0026#34; } }, { \u0026#34;key\u0026#34;: \u0026#34;metadata\u0026#34;, \u0026#34;value\u0026#34;: { \u0026#34;hoge\u0026#34;: \u0026#34;fuga\u0026#34; } } ] $ cat example.yaml | yq \u0026#34;.config | to_entries\u0026#34; - key: spec111 value: aaa: 123 bbb: 456 - key: spec222 value: aaa: abc bbb: def - key: metadata value: hoge: fuga to_entriesを噛ませた結果は配列として出力されるので、\nこの配列の要素を正規表現でフィルタするためにmap, select, testを噛ませてやる。\nhttps://stedolan.github.io/jq/manual/#map(x),map_values(x)\nhttps://stedolan.github.io/jq/manual/#select(boolean_expression)\nhttps://stedolan.github.io/jq/manual/#test(val),test(regex;flags)\nhttps://mikefarah.gitbook.io/yq/operators/map\nhttps://mikefarah.gitbook.io/yq/operators/select\nhttps://mikefarah.gitbook.io/yq/operators/string-operators#test-using-regex\nselect, testの条件にto_entriesで追加された.keyを指定することでキー名で正規表現をかけることができる。\n$ regex=\u0026#39;^spec[0-9]+$\u0026#39; $ cat example.json | jq \u0026#34;.config | to_entries | map(select(.key|test(\\\u0026#34;${regex}\\\u0026#34;)))\u0026#34; [ { \u0026#34;key\u0026#34;: \u0026#34;spec111\u0026#34;, \u0026#34;value\u0026#34;: { \u0026#34;aaa\u0026#34;: 123, \u0026#34;bbb\u0026#34;: 456 } }, { \u0026#34;key\u0026#34;: \u0026#34;spec222\u0026#34;, \u0026#34;value\u0026#34;: { \u0026#34;aaa\u0026#34;: \u0026#34;abc\u0026#34;, \u0026#34;bbb\u0026#34;: \u0026#34;def\u0026#34; } } ] $ cat example.yaml | yq \u0026#34;.config | to_entries | map(select(.key|test(\\\u0026#34;${regex}\\\u0026#34;)))\u0026#34; - key: spec111 value: aaa: 123 bbb: 456 - key: spec222 value: aaa: abc bbb: def あとはto_entriesのフォーマットの配列をfrom_entriesで元のキーと値の形式に戻してやる。\n$ regex=\u0026#39;^spec[0-9]+$\u0026#39; $ cat example.json | jq \u0026#34;.config | to_entries | map(select(.key|test(\\\u0026#34;${regex}\\\u0026#34;))) | from_entries\u0026#34; { \u0026#34;spec111\u0026#34;: { \u0026#34;aaa\u0026#34;: 123, \u0026#34;bbb\u0026#34;: 456 }, \u0026#34;spec222\u0026#34;: { \u0026#34;aaa\u0026#34;: \u0026#34;abc\u0026#34;, \u0026#34;bbb\u0026#34;: \u0026#34;def\u0026#34; } } $ cat example.yaml | yq \u0026#34;.config | to_entries | map(select(.key|test(\\\u0026#34;${regex}\\\u0026#34;))) | from_entries\u0026#34; spec111: aaa: 123 bbb: 456 spec222: aaa: abc bbb: def 最初のコマンドと同じ結果が得られた。\nおわり jq(yq)でオブジェクトのキーに対して正規表現でフィルタをかけることができた。\nマジで便利。\nおまけ ","date":"2023-02-13T00:00:00Z","image":"/post/2023-02-13-jq-yq-regex-for-keys/sotochan.jpeg","permalink":"/post/2023-02-13-jq-yq-regex-for-keys/","title":"jq/yqでオブジェクトのキーを正規表現でフィルタする"},{"content":"まとめ こたつの上 前からちょいちょいこたつの上でごろごろしていたそとちゃん。\nたまたまそこにタオルを放置していたら、いつのまにかそとちゃんのものになってしまった。\nタオルすき pic.twitter.com/KDL1YSQVtb\n\u0026mdash; ずみし (@uzimihsr) January 13, 2023 あったかくて肌触りがやわらかいのが気に入った？\nせまいのにはみ出さないように乗っかっててかわいい。\nおまんじゅう pic.twitter.com/IltrJi9gfu\n\u0026mdash; ずみし (@uzimihsr) January 20, 2023 訂正。\nはみ出てるときもある。\nとけた pic.twitter.com/8e16Anh8DC\n\u0026mdash; ずみし (@uzimihsr) January 17, 2023 この後一回タオルにうっかり吐いてしまい洗うことになったが、\nその間すげー悲しそうだったのでよっぽど気に入ったんだと思う。\n(なかったらなかったでこたつの上には乗る)\nドア あったかい時期は部屋のドアを開けっぱなしにしていたのだが、\n最近は廊下の冷気がキツいので頻繁に閉めている。\nそしてそとちゃんは自力でドアが開けられない。\n(世の中には器用にドアノブを回しちゃうねこもいるみたいだけど\u0026hellip;)\nドアを開けてほしいねこ pic.twitter.com/Fdq16nBJGC\n\u0026mdash; ずみし (@uzimihsr) January 31, 2023 廊下を走り回るのが好きなそとちゃんはこれが不満で、\n昼夜問わずドアの前でわんわん鳴いている。\n仕方がないので開けてあげるとそとちゃんが廊下に飛び出す。\nうっかり締め出してしまわないように、戻ってくるのを待ってからドアを閉める。\nまたそとちゃんが走りたくなって鳴く。\n日中はこれがずっと続いている。\n猫は寒い所が苦手とはなんだったのか。\nなぜ廊下より広くて暖かい部屋で遊ばないのか。\n謎である。\nでも開けてあげたときのそとちゃんがめちゃごきげんでかわいいのでOKです。\nドア開けてもらって機嫌良い pic.twitter.com/GlBBhdU60b\n\u0026mdash; ずみし (@uzimihsr) January 24, 2023 おわり 年があけてさらに寒くなってきたけど、そとちゃんは元気に寒い廊下を走り回っている。\nごはんもよく食べるし(カリカリも食べる)、うんちももりもり出して健康でなにより。\nそとちゃんが今年も健康を維持できるように頑張りたい。\nおまけ ","date":"2023-02-10T00:00:00Z","image":"/post/2023-02-10-sotochan/sotochan.jpeg","permalink":"/post/2023-02-10-sotochan/","title":"1月のそとちゃん(2023)"},{"content":"まとめ こたつ 寝落ち クリスマス こたつ めちゃんこ寒くなってきたのでついにこたつを出した。\nこたつ pic.twitter.com/deHYXBAjS3\n\u0026mdash; ずみし (@uzimihsr) December 17, 2022 そとちゃんはこたつが大好き。\nほぼ毎日中で寝ている。\n普段布団なんか絶対入らないのに(被せると逃げる)、こたつには自分から器用に潜っていく。\nぬるり pic.twitter.com/9IGxBZdyfG\n\u0026mdash; ずみし (@uzimihsr) December 18, 2022 ちなみにそとちゃんがこたつにいるときに俺が後から入ると高確率で出ていってしまう。\n冷たいつま先が触れるのがどうしても嫌みたい。\n(ただ単に俺の足が臭い説もある)\n俺がこたつ入ったのが気に入らない様子 pic.twitter.com/GEPcdQk9L0\n\u0026mdash; ずみし (@uzimihsr) December 29, 2022 それでもそとちゃんが後からくるときはこたつの上に乗ってくれるようになって、\nうまいこと共存できている。\nこたつの上もあったかいことに気づいた pic.twitter.com/1DjQsydKbE\n\u0026mdash; ずみし (@uzimihsr) December 28, 2022 が、欲を言えばやっぱり一緒に入りたい。\nかなしい\u0026hellip;\n寝落ち 前述のとおりそとちゃんはほぼ毎日こたつの中で溶けているのだが、\n日が差す時間になるとちゃんと窓際でひなたぼっこする。\n超ごきげん pic.twitter.com/vHZiSXGNh8\n\u0026mdash; ずみし (@uzimihsr) December 12, 2022 問題はその後で、 なぜかそとちゃんはこたつに戻れずカーテンの裾で寝落ちしてしまう。\n光が多く入るように毎日カーテンの裾を上げているのだが、\nある日裾を縛るのがめんどくさくてベッドの上にそのまま放置したら気に入られてしまった。\nこたつにいないと思ったら変なとこで寝てた pic.twitter.com/jQ5G1tGK1z\n\u0026mdash; ずみし (@uzimihsr) December 22, 2022 理由はよくわかんない。謎。\n生地の肌触りが好きなのか\u0026hellip;？\n裾を縛ってるときはすぐこたつに戻るので、場所というよりはカーテンがよっぽど気に入った様子。\n珍しいときにはまだひなたぼっこできる時間でもここで寝てることがある。\nまたカーテン踏んづけて寝てる pic.twitter.com/lJDLNRui70\n\u0026mdash; ずみし (@uzimihsr) December 23, 2022 よくわからない。\nクリスマス 去年は手術後でクリスマスっぽいことがあまりできなかったが、\n今年はそとちゃんに変身してもらった。\nクリスマスねこちゃん pic.twitter.com/bPFOwb97e1\n\u0026mdash; ずみし (@uzimihsr) December 24, 2022 今回は赤いマント。\nあんまり重くないし、動きも制限されないので結構気に入ったみたい。\n人間のわがままに付き合ってもらったので、報酬としてごちそうを献上。\nごちそう pic.twitter.com/FjoYqyJ9Il\n\u0026mdash; ずみし (@uzimihsr) December 24, 2022 翌朝はプレゼントを開封。\n去年あんまりお祝いできなかったので、今年のクリスマスプレゼントは気持ち多めになった。\n🎅🎁 pic.twitter.com/I4PuPQqSSf\n\u0026mdash; ずみし (@uzimihsr) December 25, 2022 今まで持ってなかった光を追いかける系のおもちゃを手に入れたのだが、\nそとちゃんはかしこい(?)ので、ライトで照らされる場所でなくライトの発光部分を直接追いかけてしまう。\nちなみにこのおもちゃの光はレーザーではない(LED)のでそれらに比べれば安全という話だが、\n実際のところどうなんだろう\u0026hellip;?\n直視して良いものでは無い気がしたので、結局このおもちゃはほぼお蔵入りとなってしまった。\n遊び方が違う pic.twitter.com/1vQiSt2qhc\n\u0026mdash; ずみし (@uzimihsr) December 25, 2022 こんなこともあろうかと(?)\nクリスマスに関係なく 100 均で適当に買ってきたおもちゃを出したらこちらにめちゃめちゃ食いついた。\n楽しく過ごせてよかった。\nおわり 今年のそとちゃんは大きな病気や怪我もなく健康で何よりだった。\nそとちゃん的に良い 1 年だったかはよくわからないが、 俺的にはまあまあだったと思う。\n大きなイベントとして引越しがあったけどすぐに慣れてくれたし、\n引越しの目的でもあったひなたぼっこをたくさんしてくれたので嬉しかった。\nそとちゃんは来年いよいよシニアねこ(たぶん 7 歳)になる。\nまだまだうるさいくらいに元気いっぱいだけど、これからも元気に過ごせるように気をつけたい。\nおまけ ","date":"2022-12-31T00:00:00Z","image":"/post/2022-12-31-sotochan/sotochan.jpeg","permalink":"/post/2022-12-31-sotochan/","title":"12月のそとちゃん(2022)"},{"content":"まとめ 日課 新ベッド えらい 日課 そとちゃんは11月もひなたぼっこを毎日がんばっていた。\nくわ〜っ pic.twitter.com/aobiS002r9\n\u0026mdash; ずみし (@uzimihsr) November 7, 2022 日が入ってくる時間帯になると窓際で待機。\n鳥が来ないか少しだけ見張って(最近あんまりこない)、\nあとは横になって満足するまでしっかり日を浴びる。\nいそがしい pic.twitter.com/oeN8RYWEKu\n\u0026mdash; ずみし (@uzimihsr) November 16, 2022 (だいたい寝落ちする)\nあんまり元気じゃない日もひなたぼっこは欠かさない。\nまるで仕事のよう。\n機嫌悪い pic.twitter.com/e9NowqEQ0t\n\u0026mdash; ずみし (@uzimihsr) November 25, 2022 天気が悪い日も一応窓際に出勤する。\nハンモックの上で日が出るのをずっと待っていて、\nそのまま晴れなかったら文句を言いながら降りてくる。\n\u0026hellip;毎日と言ったがあれは嘘だった。\n早朝の運動で疲れすぎて夕方まで寝てる日もある。\n今日はひなたぼっこサボって寝てた pic.twitter.com/dvWQD36L6H\n\u0026mdash; ずみし (@uzimihsr) November 4, 2022 自分のペースでがんばってほしい(?)。\n新ベッド 10月後半あたりからそうなんだけどお気に入りの場所がまた増えた。\n今までもあったバリバリボウルにブランケットかけただけのベッドがいい感じ。\nテーブルの下に置いたらやたらと乗る。\nこたつ出せアピール？\nひなたぼっこしてないときはだいたいここだし、\n最近は寝る時も本棚の上よりこっちにいることが多い。\nよくわかんないけど気に入ってくれてよかった。\nえらい 朝昼のカリカリ完食率が上がってきた。\n理由は謎だけどトッピング付与率の仕組みに気づいたか、部屋が乾燥してきてカリカリの風味が良くなった？\nうまいうまい pic.twitter.com/dwdzWzXDwR\n\u0026mdash; ずみし (@uzimihsr) November 12, 2022 おかげでちゅーるごはんの機会も増えていい感じ。\nマシマシ pic.twitter.com/CbD7S9AT00\n\u0026mdash; ずみし (@uzimihsr) November 15, 2022 でも毎日カリカリ完食って感じではなくて、\nたまに食べ忘れてノートッピングになることもある。\nプレーン猫缶 pic.twitter.com/xy1FYeo6m3\n\u0026mdash; ずみし (@uzimihsr) November 14, 2022 ちなみにそとちゃんはかしこいので絶対に美味しいところから食べる。\n絶対トッピングから食べる pic.twitter.com/5LXKYrZESC\n\u0026mdash; ずみし (@uzimihsr) November 29, 2022 カリカリを一口も食べずに残したりしてた夏頃に比べると格段に良くなった。\nえらい！\nおわり だんだん寒くなってきたが11月もそとちゃんはよく寝てよく食べて元気だった。\n新居の断熱性能がそこそこいい感じで11月まではエアコンだけで寒さをしのいだが、\nここからもっと寒くなるはずなのでこたつの出番は近いかもしれない。\nおまけ ","date":"2022-12-07T00:00:00Z","image":"/post/2022-12-07-sotochan/sotochan.jpeg","permalink":"/post/2022-12-07-sotochan/","title":"11月のそとちゃん(2022)"},{"content":"Summary This is a working log of the installation of kube-state-metrics, cAdvisor, and Prometheus on a Kubernetes cluster.\nCreate a single-node Kubernetes cluster with kind Install kube-state-metrics as a Deployment Install cAdvisor as a DaemonSet Install Prometheus as a Deployment and configure it to scrape these metrics Prerequisites kind v0.17.0 go1.19.2 darwin/arm64 kubernetes v1.25.3 kubectl v1.25.0 prometheus version 2.40.3 kube-state-metrics v2.7.0 cAdvisor v0.45.0 Logs Create a Kubernetes cluster Let\u0026rsquo;s go with kind.\n$ kind create cluster Creating cluster \u0026#34;kind\u0026#34; ... ✓ Ensuring node image (kindest/node:v1.25.3) 🖼 ✓ Preparing nodes 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 Set kubectl context to \u0026#34;kind-kind\u0026#34; You can now use your cluster with: kubectl cluster-info --context kind-kind Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂 Install kube-state-metrics kube-state-metrics generates the metrics of Kubernetes objects such as Nodes, Services and Pods.\nYou can install it using the sample manifests.\n$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/kube-state-metrics/master/examples/standard/service-account.yaml \u0026amp;\u0026amp; \\ kubectl apply -f https://raw.githubusercontent.com/kubernetes/kube-state-metrics/master/examples/standard/cluster-role.yaml \u0026amp;\u0026amp; \\ kubectl apply -f https://raw.githubusercontent.com/kubernetes/kube-state-metrics/master/examples/standard/cluster-role-binding.yaml \u0026amp;\u0026amp; \\ kubectl apply -f https://raw.githubusercontent.com/kubernetes/kube-state-metrics/master/examples/standard/deployment.yaml \u0026amp;\u0026amp; \\ kubectl apply -f https://raw.githubusercontent.com/kubernetes/kube-state-metrics/master/examples/standard/service.yaml serviceaccount/kube-state-metrics created clusterrole.rbac.authorization.k8s.io/kube-state-metrics created clusterrolebinding.rbac.authorization.k8s.io/kube-state-metrics created deployment.apps/kube-state-metrics created service/kube-state-metrics created In this case, kube-state-metrics is run as a Deployment.\nLet\u0026rsquo;s port-forward to this Service and check the metrics.\n$ kubectl -n kube-system get deploy,service -l app.kubernetes.io/name=kube-state-metrics NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/kube-state-metrics 1/1 1 1 115s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kube-state-metrics ClusterIP None \u0026lt;none\u0026gt; 8080/TCP,8081/TCP 115s $ kubectl -n kube-system port-forward service/kube-state-metrics 8080:8080 8081:8081 Forwarding from 127.0.0.1:8080 -\u0026gt; 8080 Forwarding from [::1]:8080 -\u0026gt; 8080 Forwarding from 127.0.0.1:8081 -\u0026gt; 8081 Forwarding from [::1]:8081 -\u0026gt; 8081 ... # from different terminal $ curl localhost:8080/metrics ... # HELP kube_configmap_labels [STABLE] Kubernetes labels converted to Prometheus labels. # TYPE kube_configmap_labels gauge kube_configmap_labels{namespace=\u0026#34;local-path-storage\u0026#34;,configmap=\u0026#34;local-path-config\u0026#34;} 1 kube_configmap_labels{namespace=\u0026#34;default\u0026#34;,configmap=\u0026#34;kube-root-ca.crt\u0026#34;} 1 ... Good. It works fine.\nThe docs has more information about the metrics.\nkube-state-metrics can also be accessed via a browser.\nhttp://localhost:8080/\nhttp://localhost:8081/\nInstall cAdvisor cAdvisor exports the container metrics such as CPU usage, Memory usage, etc.\nKustomized manifests are available. Yes!\n$ kubectl apply -k https://github.com/google/cadvisor//deploy/kubernetes/base namespace/cadvisor created serviceaccount/cadvisor created daemonset.apps/cadvisor created cAdvisor runs as a DaemonSet and collects container information from each Node.\n$ kubectl -n cadvisor get daemonset -l app=cadvisor NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE cadvisor 1 1 1 1 1 \u0026lt;none\u0026gt; 6m54s $ pod=`kubectl -n cadvisor get pod -l app=cadvisor -o jsonpath=\u0026#34;{.items[0].metadata.name}\u0026#34;` $ kubectl -n cadvisor port-forward pod/\u0026#34;${pod}\u0026#34; 8080:8080 Forwarding from 127.0.0.1:8080 -\u0026gt; 8080 Forwarding from [::1]:8080 -\u0026gt; 8080 # from different terminal $ curl localhost:8080/metrics # HELP cadvisor_version_info A metric with a constant \u0026#39;1\u0026#39; value labeled by kernel version, OS version, docker version, cadvisor version \u0026amp; cadvisor revision. # TYPE cadvisor_version_info gauge cadvisor_version_info{cadvisorRevision=\u0026#34;86b11c65\u0026#34;,cadvisorVersion=\u0026#34;v0.45.0\u0026#34;,dockerVersion=\u0026#34;\u0026#34;,kernelVersion=\u0026#34;5.10.124-linuxkit\u0026#34;,osVersion=\u0026#34;Alpine Linux v3.16\u0026#34;} 1 # HELP container_blkio_device_usage_total Blkio Device bytes usage # TYPE container_blkio_device_usage_total counter container_blkio_device_usage_total{container_label_app=\u0026#34;\u0026#34;,container_label_app_kubernetes_io_component=\u0026#34;\u0026#34;,container_label_app_kubernetes_io_name=\u0026#34;\u0026#34;,container_label_app_kubernetes_io_version=\u0026#34;\u0026#34;,container_label_component=\u0026#34;\u0026#34;,container_label_controller_revision_hash=\u0026#34;\u0026#34;,container_label_description=\u0026#34;\u0026#34;,container_label_io_cri_containerd_kind=\u0026#34;\u0026#34;,container_label_io_kubernetes_container_name=\u0026#34;\u0026#34;,container_label_io_kubernetes_pod_name=\u0026#34;\u0026#34;,container_label_io_kubernetes_pod_namespace=\u0026#34;\u0026#34;,container_label_io_kubernetes_pod_uid=\u0026#34;\u0026#34;,container_label_k8s_app=\u0026#34;\u0026#34;,container_label_maintainers=\u0026#34;\u0026#34;,container_label_name=\u0026#34;\u0026#34;,container_label_pod_template_generation=\u0026#34;\u0026#34;,container_label_pod_template_hash=\u0026#34;\u0026#34;,container_label_tier=\u0026#34;\u0026#34;,device=\u0026#34;/dev/vda\u0026#34;,id=\u0026#34;/\u0026#34;,image=\u0026#34;\u0026#34;,major=\u0026#34;254\u0026#34;,minor=\u0026#34;0\u0026#34;,name=\u0026#34;\u0026#34;,operation=\u0026#34;Read\u0026#34;} 0 1669642901054 ... Metrics with names like container_** are container metrics.\nOf course, cAdvisor can also be accessed via a browser.\nhttp://localhost:8080/\nInstall Prometheus Next, install Prometheus to scrape these metrics.\nHere is an example manifest that installs Prometheus as a Deployment.\nApply it.\n$ kubectl create namespace prometheus namespace/prometheus created $ kubectl -n prometheus apply -f deployment.yaml deployment.apps/prometheus created $ kubectl -n prometheus expose deployment prometheus --port=9090 --target-port=9090 service/prometheus exposed Make sure it is working properly.\n$ kubectl -n prometheus port-forward service/prometheus 9090:9090 Forwarding from 127.0.0.1:9090 -\u0026gt; 9090 Forwarding from [::1]:9090 -\u0026gt; 9090 http://localhost:9090/\nGood.\nConfigure Prometheus for service discovery (kubernetes_sd_config) Finally, configure Prometheus to scrape metrics in the cluster.\nkubernetes_sd_config is the key.\nFollow the example and create prometheus.yml.\nIn this case, Prometheus scrapes only Services of kube-state-metrics and Pods of cAdvisor.\n(Without this config, kube-state-metrics is scraped by both \u0026ldquo;role: service\u0026rdquo; job and \u0026ldquo;role: pod\u0026rdquo; job, then the metrics are duplicated.)\n$ kubectl -n cadvisor get pods --show-labels NAME READY STATUS RESTARTS AGE LABELS cadvisor-fngj6 1/1 Running 0 23h app=cadvisor,controller-revision-hash=df8bf66b4,name=cadvisor,pod-template-generation=1 $ kubectl -n kube-system get service kube-state-metrics --show-labels NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE LABELS kube-state-metrics ClusterIP None \u0026lt;none\u0026gt; 8080/TCP,8081/TCP 24h app.kubernetes.io/component=exporter,app.kubernetes.io/name=kube-state-metrics,app.kubernetes.io/version=2.7.0 $ kubectl apply -f configmap.yaml configmap/prometheus-config created Update the Deployment to load the config.\n$ kubectl apply -f deployment.yaml deployment.apps/prometheus configured Open Status-\u0026gt;Service Discovery to check operation.\nhttp://localhost:9090/service-discovery\nOops! Service discovery has failed.\nCheck the Pod log\u0026hellip;\n$ pod=`kubectl -n prometheus get pods -l app=prometheus -o jsonpath=\u0026#34;{.items[0].metadata.name}\u0026#34;` $ kubectl -n prometheus logs $pod ... ts=2022-11-28T14:26:56.150Z caller=kubernetes.go:326 level=info component=\u0026#34;discovery manager scrape\u0026#34; discovery=kubernetes msg=\u0026#34;Using pod service account via in-cluster config\u0026#34; ts=2022-11-28T14:26:56.150Z caller=main.go:1234 level=info msg=\u0026#34;Completed loading of configuration file\u0026#34; filename=/config/prometheus.yml totalDuration=2.798834ms db_storage=1.208µs remote_storage=1.417µs web_handler=375ns query_engine=1.042µs scrape=250.917µs scrape_sd=2.159042ms notify=1.083µs notify_sd=5.709µs rules=1.584µs tracing=12.958µs ts=2022-11-28T14:26:56.150Z caller=main.go:978 level=info msg=\u0026#34;Server is ready to receive web requests.\u0026#34; ts=2022-11-28T14:26:56.150Z caller=manager.go:944 level=info component=\u0026#34;rule manager\u0026#34; msg=\u0026#34;Starting rule manager...\u0026#34; ts=2022-11-28T14:26:56.156Z caller=klog.go:108 level=warn component=k8s_client_runtime func=Warningf msg=\u0026#34;pkg/mod/k8s.io/client-go@v0.25.3/tools/cache/reflector.go:169: failed to list *v1.Service: services is forbidden: User \\\u0026#34;system:serviceaccount:prometheus:default\\\u0026#34; cannot list resource \\\u0026#34;services\\\u0026#34; in API group \\\u0026#34;\\\u0026#34; at the cluster scope\u0026#34; ts=2022-11-28T14:26:56.156Z caller=klog.go:108 level=warn component=k8s_client_runtime func=Warningf msg=\u0026#34;pkg/mod/k8s.io/client-go@v0.25.3/tools/cache/reflector.go:169: failed to list *v1.Pod: pods is forbidden: User \\\u0026#34;system:serviceaccount:prometheus:default\\\u0026#34; cannot list resource \\\u0026#34;pods\\\u0026#34; in API group \\\u0026#34;\\\u0026#34; at the cluster scope\u0026#34; ts=2022-11-28T14:26:56.156Z caller=klog.go:116 level=error component=k8s_client_runtime func=ErrorDepth msg=\u0026#34;pkg/mod/k8s.io/client-go@v0.25.3/tools/cache/reflector.go:169: Failed to watch *v1.Service: failed to list *v1.Service: services is forbidden: User \\\u0026#34;system:serviceaccount:prometheus:default\\\u0026#34; cannot list resource \\\u0026#34;services\\\u0026#34; in API group \\\u0026#34;\\\u0026#34; at the cluster scope\u0026#34; ts=2022-11-28T14:26:56.156Z caller=klog.go:108 level=warn component=k8s_client_runtime func=Warningf msg=\u0026#34;pkg/mod/k8s.io/client-go@v0.25.3/tools/cache/reflector.go:169: failed to list *v1.Endpoints: endpoints is forbidden: User \\\u0026#34;system:serviceaccount:prometheus:default\\\u0026#34; cannot list resource \\\u0026#34;endpoints\\\u0026#34; in API group \\\u0026#34;\\\u0026#34; at the cluster scope\u0026#34; ts=2022-11-28T14:26:56.156Z caller=klog.go:116 level=error component=k8s_client_runtime func=ErrorDepth msg=\u0026#34;pkg/mod/k8s.io/client-go@v0.25.3/tools/cache/reflector.go:169: Failed to watch *v1.Endpoints: failed to list *v1.Endpoints: endpoints is forbidden: User \\\u0026#34;system:serviceaccount:prometheus:default\\\u0026#34; cannot list resource \\\u0026#34;endpoints\\\u0026#34; in API group \\\u0026#34;\\\u0026#34; at the cluster scope\u0026#34; ... According to the log, Prometheus is failing to get information of Pods, Services and Endpoints because the default ServiceAccount is not authorized.\nThen create ServiceAccount, ClusterRole and ClusterRoleBinding to gave permission.\n$ kubectl -n prometheus create serviceaccount prometheus serviceaccount/prometheus created $ kubectl create clusterrole prometheus --verb=get,list,watch --resource=pods,services,endpoints clusterrole.rbac.authorization.k8s.io/prometheus created $ kubectl create clusterrolebinding prometheus --clusterrole=prometheus --serviceaccount=prometheus:prometheus clusterrolebinding.rbac.authorization.k8s.io/prometheus created Update the Deployment again.\n$ kubectl apply -f deployment.yaml deployment.apps/prometheus configured This time it succeeded. Wow!\nThe metrics are correctly scraped.\nThat is all.\n","date":"2022-11-28T00:00:00Z","image":"/post/2022-11-28-kubernetes-prometheus-kube-state-metrics-cadvisor/sotochan.jpeg","permalink":"/post/2022-11-28-kubernetes-prometheus-kube-state-metrics-cadvisor/","title":"Install kube-state-metrics, cAdvisor and Prometheus on a Kubernetes cluster"},{"content":"やったことのまとめ 自分は普段すでに監視用アドオンが入った状態のマネージドKubernetesクラスタを使うのが当たり前になっている。\nそれがどんなにありがたいことか分かってない気がしたのと、\n単純にそれらの構成がどうなっているのか興味があったので実際に手を動かして設定してみた。\nお試し用にkindでKubernetesクラスタ作成 kube-state-metrics(Deployment)でKubernetesメトリクスを収集 cAdvisor(DaemonSet)でコンテナメトリクスを収集 Prometheus(Deployment)にService Discovery設定をして上記2種のメトリクスをモニタリング つかうもの kind v0.17.0 go1.19.2 darwin/arm64 kubernetes v1.25.3 kubectl v1.25.0 prometheus version 2.40.3 kube-state-metrics v2.7.0 cAdvisor v0.45.0 やったこと Kubernetesクラスタの作成 今回は特に構成にこだわりもないので適当にkindでシングルノードのKubernetesクラスタをつくる。\n$ kind create cluster Creating cluster \u0026#34;kind\u0026#34; ... ✓ Ensuring node image (kindest/node:v1.25.3) 🖼 ✓ Preparing nodes 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 Set kubectl context to \u0026#34;kind-kind\u0026#34; You can now use your cluster with: kubectl cluster-info --context kind-kind Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂 kube-state-metricsのインストール Kubernetesリソース(Podとか)のメトリクスといったらkube-state-metricsって感じなのでまずはこいつを入れていく。\nサンプルマニフェストが公開されているのでそれを使ってみる。\n$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/kube-state-metrics/master/examples/standard/service-account.yaml \u0026amp;\u0026amp; \\ kubectl apply -f https://raw.githubusercontent.com/kubernetes/kube-state-metrics/master/examples/standard/cluster-role.yaml \u0026amp;\u0026amp; \\ kubectl apply -f https://raw.githubusercontent.com/kubernetes/kube-state-metrics/master/examples/standard/cluster-role-binding.yaml \u0026amp;\u0026amp; \\ kubectl apply -f https://raw.githubusercontent.com/kubernetes/kube-state-metrics/master/examples/standard/deployment.yaml \u0026amp;\u0026amp; \\ kubectl apply -f https://raw.githubusercontent.com/kubernetes/kube-state-metrics/master/examples/standard/service.yaml serviceaccount/kube-state-metrics created clusterrole.rbac.authorization.k8s.io/kube-state-metrics created clusterrolebinding.rbac.authorization.k8s.io/kube-state-metrics created deployment.apps/kube-state-metrics created service/kube-state-metrics created この手順でマニフェストをapplyするとkube-state-metricsがDeploymentとして動作する。\nServiceも一緒に作られているのでport-forwardしてメトリクスを見てみる。\n$ kubectl -n kube-system get deploy,service -l app.kubernetes.io/name=kube-state-metrics NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/kube-state-metrics 1/1 1 1 115s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kube-state-metrics ClusterIP None \u0026lt;none\u0026gt; 8080/TCP,8081/TCP 115s $ kubectl -n kube-system port-forward service/kube-state-metrics 8080:8080 8081:8081 Forwarding from 127.0.0.1:8080 -\u0026gt; 8080 Forwarding from [::1]:8080 -\u0026gt; 8080 Forwarding from 127.0.0.1:8081 -\u0026gt; 8081 Forwarding from [::1]:8081 -\u0026gt; 8081 ... # from different terminal $ curl localhost:8080/metrics ... # HELP kube_configmap_labels [STABLE] Kubernetes labels converted to Prometheus labels. # TYPE kube_configmap_labels gauge kube_configmap_labels{namespace=\u0026#34;local-path-storage\u0026#34;,configmap=\u0026#34;local-path-config\u0026#34;} 1 kube_configmap_labels{namespace=\u0026#34;default\u0026#34;,configmap=\u0026#34;kube-root-ca.crt\u0026#34;} 1 ... こんな感じでkube-state-metricsはKubernetes APIをポーリングして得られたPodのステータスとかの情報をメトリクス化している。らしい。\nちなみにメトリクスの詳細な説明はdocsで公開されている。\n実はUIも用意されているが、今回の目的ではないので割愛。\nhttp://localhost:8080/\nhttp://localhost:8081/\ncAdvisorのインストール cAdvisorもコンテナ単位のメトリクスが見られて便利なのでこれも入れていく。\nこちらはサンプルマニフェストがkustomize化されているのでさらに簡単に入れられる。\n$ kubectl apply -k https://github.com/google/cadvisor//deploy/kubernetes/base namespace/cadvisor created serviceaccount/cadvisor created daemonset.apps/cadvisor created cAdvisorは各Node上のコンテナ情報を取得するためDaemonSetとして動作している。\n(昔はkubeletに勝手に入ってた気がするが、最近はDaemonSetで動かすのが普通っぽい?)\nkube-state-metricsと同様にport-forwardしてメトリクスを確認する。\n$ kubectl -n cadvisor get daemonset -l app=cadvisor NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE cadvisor 1 1 1 1 1 \u0026lt;none\u0026gt; 6m54s $ pod=`kubectl -n cadvisor get pod -l app=cadvisor -o jsonpath=\u0026#34;{.items[0].metadata.name}\u0026#34;` $ kubectl -n cadvisor port-forward pod/\u0026#34;${pod}\u0026#34; 8080:8080 Forwarding from 127.0.0.1:8080 -\u0026gt; 8080 Forwarding from [::1]:8080 -\u0026gt; 8080 # from different terminal $ curl localhost:8080/metrics # HELP cadvisor_version_info A metric with a constant \u0026#39;1\u0026#39; value labeled by kernel version, OS version, docker version, cadvisor version \u0026amp; cadvisor revision. # TYPE cadvisor_version_info gauge cadvisor_version_info{cadvisorRevision=\u0026#34;86b11c65\u0026#34;,cadvisorVersion=\u0026#34;v0.45.0\u0026#34;,dockerVersion=\u0026#34;\u0026#34;,kernelVersion=\u0026#34;5.10.124-linuxkit\u0026#34;,osVersion=\u0026#34;Alpine Linux v3.16\u0026#34;} 1 # HELP container_blkio_device_usage_total Blkio Device bytes usage # TYPE container_blkio_device_usage_total counter container_blkio_device_usage_total{container_label_app=\u0026#34;\u0026#34;,container_label_app_kubernetes_io_component=\u0026#34;\u0026#34;,container_label_app_kubernetes_io_name=\u0026#34;\u0026#34;,container_label_app_kubernetes_io_version=\u0026#34;\u0026#34;,container_label_component=\u0026#34;\u0026#34;,container_label_controller_revision_hash=\u0026#34;\u0026#34;,container_label_description=\u0026#34;\u0026#34;,container_label_io_cri_containerd_kind=\u0026#34;\u0026#34;,container_label_io_kubernetes_container_name=\u0026#34;\u0026#34;,container_label_io_kubernetes_pod_name=\u0026#34;\u0026#34;,container_label_io_kubernetes_pod_namespace=\u0026#34;\u0026#34;,container_label_io_kubernetes_pod_uid=\u0026#34;\u0026#34;,container_label_k8s_app=\u0026#34;\u0026#34;,container_label_maintainers=\u0026#34;\u0026#34;,container_label_name=\u0026#34;\u0026#34;,container_label_pod_template_generation=\u0026#34;\u0026#34;,container_label_pod_template_hash=\u0026#34;\u0026#34;,container_label_tier=\u0026#34;\u0026#34;,device=\u0026#34;/dev/vda\u0026#34;,id=\u0026#34;/\u0026#34;,image=\u0026#34;\u0026#34;,major=\u0026#34;254\u0026#34;,minor=\u0026#34;0\u0026#34;,name=\u0026#34;\u0026#34;,operation=\u0026#34;Read\u0026#34;} 0 1669642901054 ... container_**みたいな名前のメトリクスがとれていることがわかる。\nこちらも実はUIが用意されているが割愛。\nhttp://localhost:8080/\nPrometheusのインストール Kubernetesリソースとコンテナのメトリクスが用意できたので、\n次にこれをモニタリングするPrometheusを用意する。\nちょっと探したけどPrometheusについては公式っぽいKubernetesマニフェスト例が見つからなかったので自分でちょろっと書いて立ててみる。\n$ kubectl create namespace prometheus namespace/prometheus created $ kubectl -n prometheus apply -f deployment.yaml deployment.apps/prometheus created $ kubectl -n prometheus expose deployment prometheus --port=9090 --target-port=9090 service/prometheus exposed 今回は特にメトリクスを永続化したいわけではなかったのでPrometheusをDeploymentとして立てた。\n(メトリクスを永続化したいときはStatefulSetで立てたり、クラスタ外にFederateしたりいろいろやり方はあるとおもう)\nこれも同様にport-forwardしてUIを開いて動作確認する。\n$ kubectl -n prometheus port-forward service/prometheus 9090:9090 Forwarding from 127.0.0.1:9090 -\u0026gt; 9090 Forwarding from [::1]:9090 -\u0026gt; 9090 http://localhost:9090/\nいい感じ。\nPrometheusの監視設定(kubernetes_sd_config) 最後にこのPrometheusでkube-state-metricsとcAdvisorのメトリクスが取れるように設定していく。\nKubernetesクラスタ内のメトリクスをPrometheusで監視する場合static_configにいちいちServiceやPodのIPを羅列していては日が暮れてしまうし、\nそれらが変化してしまったときに動的に追従させるのが大変というかたぶん無理。\nこのため通常はkubernetes_sd_configを使ってクラスタ内のServiceやPodの宛先をPrometheusが勝手に探すようにする(Service Discovery)。\nこれについては公式の例があるのでこちらを参考にさせてもらう。\n今回はrelabel_configsの設定でモニタリングの対象をkube-state-metricsのServiceとcAdvisorのDaemonSet(Pod)に絞っている。\n(role: podのsd設定を入れると全Podのメトリクスが対象となりrole: serviceのsd設定で対象となっているメトリクスと重複するため)\n$ kubectl -n cadvisor get pods --show-labels NAME READY STATUS RESTARTS AGE LABELS cadvisor-fngj6 1/1 Running 0 23h app=cadvisor,controller-revision-hash=df8bf66b4,name=cadvisor,pod-template-generation=1 $ kubectl -n kube-system get service kube-state-metrics --show-labels NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE LABELS kube-state-metrics ClusterIP None \u0026lt;none\u0026gt; 8080/TCP,8081/TCP 24h app.kubernetes.io/component=exporter,app.kubernetes.io/name=kube-state-metrics,app.kubernetes.io/version=2.7.0 $ kubectl apply -f configmap.yaml configmap/prometheus-config created ついでにこのConfigMapを読み込むようにDeploymentの設定も修正する。\n$ kubectl apply -f deployment.yaml deployment.apps/prometheus configured この状態でPrometheusのService Discoveryの状態を確認する。\nhttp://localhost:9090/service-discovery\n\u0026hellip;あれ？\nなんかできてなさそう。\nPodのログを確認してみる。\n$ pod=`kubectl -n prometheus get pods -l app=prometheus -o jsonpath=\u0026#34;{.items[0].metadata.name}\u0026#34;` $ kubectl -n prometheus logs $pod ... ts=2022-11-28T14:26:56.150Z caller=kubernetes.go:326 level=info component=\u0026#34;discovery manager scrape\u0026#34; discovery=kubernetes msg=\u0026#34;Using pod service account via in-cluster config\u0026#34; ts=2022-11-28T14:26:56.150Z caller=main.go:1234 level=info msg=\u0026#34;Completed loading of configuration file\u0026#34; filename=/config/prometheus.yml totalDuration=2.798834ms db_storage=1.208µs remote_storage=1.417µs web_handler=375ns query_engine=1.042µs scrape=250.917µs scrape_sd=2.159042ms notify=1.083µs notify_sd=5.709µs rules=1.584µs tracing=12.958µs ts=2022-11-28T14:26:56.150Z caller=main.go:978 level=info msg=\u0026#34;Server is ready to receive web requests.\u0026#34; ts=2022-11-28T14:26:56.150Z caller=manager.go:944 level=info component=\u0026#34;rule manager\u0026#34; msg=\u0026#34;Starting rule manager...\u0026#34; ts=2022-11-28T14:26:56.156Z caller=klog.go:108 level=warn component=k8s_client_runtime func=Warningf msg=\u0026#34;pkg/mod/k8s.io/client-go@v0.25.3/tools/cache/reflector.go:169: failed to list *v1.Service: services is forbidden: User \\\u0026#34;system:serviceaccount:prometheus:default\\\u0026#34; cannot list resource \\\u0026#34;services\\\u0026#34; in API group \\\u0026#34;\\\u0026#34; at the cluster scope\u0026#34; ts=2022-11-28T14:26:56.156Z caller=klog.go:108 level=warn component=k8s_client_runtime func=Warningf msg=\u0026#34;pkg/mod/k8s.io/client-go@v0.25.3/tools/cache/reflector.go:169: failed to list *v1.Pod: pods is forbidden: User \\\u0026#34;system:serviceaccount:prometheus:default\\\u0026#34; cannot list resource \\\u0026#34;pods\\\u0026#34; in API group \\\u0026#34;\\\u0026#34; at the cluster scope\u0026#34; ts=2022-11-28T14:26:56.156Z caller=klog.go:116 level=error component=k8s_client_runtime func=ErrorDepth msg=\u0026#34;pkg/mod/k8s.io/client-go@v0.25.3/tools/cache/reflector.go:169: Failed to watch *v1.Service: failed to list *v1.Service: services is forbidden: User \\\u0026#34;system:serviceaccount:prometheus:default\\\u0026#34; cannot list resource \\\u0026#34;services\\\u0026#34; in API group \\\u0026#34;\\\u0026#34; at the cluster scope\u0026#34; ts=2022-11-28T14:26:56.156Z caller=klog.go:108 level=warn component=k8s_client_runtime func=Warningf msg=\u0026#34;pkg/mod/k8s.io/client-go@v0.25.3/tools/cache/reflector.go:169: failed to list *v1.Endpoints: endpoints is forbidden: User \\\u0026#34;system:serviceaccount:prometheus:default\\\u0026#34; cannot list resource \\\u0026#34;endpoints\\\u0026#34; in API group \\\u0026#34;\\\u0026#34; at the cluster scope\u0026#34; ts=2022-11-28T14:26:56.156Z caller=klog.go:116 level=error component=k8s_client_runtime func=ErrorDepth msg=\u0026#34;pkg/mod/k8s.io/client-go@v0.25.3/tools/cache/reflector.go:169: Failed to watch *v1.Endpoints: failed to list *v1.Endpoints: endpoints is forbidden: User \\\u0026#34;system:serviceaccount:prometheus:default\\\u0026#34; cannot list resource \\\u0026#34;endpoints\\\u0026#34; in API group \\\u0026#34;\\\u0026#34; at the cluster scope\u0026#34; ... エラーメッセージ的に\u0026quot;デフォルトで設定されたServiceAccount(prometheus:default)にPodとかServiceを見る権限が無いよ!\u0026ldquo;って感じなので、\nServiceAccount, ClusterRole, ClusterRoleBindingを作ってやる。\n$ kubectl -n prometheus create serviceaccount prometheus serviceaccount/prometheus created $ kubectl create clusterrole prometheus --verb=get,list,watch --resource=pods,services,endpoints clusterrole.rbac.authorization.k8s.io/prometheus created $ kubectl create clusterrolebinding prometheus --clusterrole=prometheus --serviceaccount=prometheus:prometheus clusterrolebinding.rbac.authorization.k8s.io/prometheus created またまたDeploymentをいじって↑で権限を付与したServiceAccountを使ってPodを立てるようにする。\n$ kubectl apply -f deployment.yaml deployment.apps/prometheus configured 再度立ち上がったPrometheusを確認すると今度はService Discoveryに成功している。\n試しにPrometheus自身のPod, コンテナのメトリクスをクエリで探すと確かに値が返ってくる。\nええやん。\nおわり Kubernetesクラスタにkube-state-metricsとcAdvisorを導入し、\nクラスタ内のPrometheusでそれらのメトリクスをモニタリングするところまでやってみた。\n今回はprometheus-operatorとか使わずに全部手作業で立てたので結構面倒だったが、\nたまにこうやって遊ぶと楽しいし、こういうやつを勝手に作成してくれる仕組みのありがたさがよくわかる気がする。\nおまけ ","date":"2022-11-28T00:00:00Z","image":"/post/2022-11-28-kubernetes-prometheus-kube-state-metrics-cadvisor-ja/sotochan.jpeg","permalink":"/post/2022-11-28-kubernetes-prometheus-kube-state-metrics-cadvisor-ja/","title":"Kubernetesクラスタにkube-state-metricsとcAdvisorをぶちこんでPrometheusで監視する"},{"content":"まとめ 毛布 トッピング ハロウィン 毛布 ちょっと寒くなってきたので毛布を出した。\n毛布出しました pic.twitter.com/KmlAMRIXk8\n\u0026mdash; ずみし (@uzimihsr) October 9, 2022 そとちゃんは毛布の上に乗るのが好き。\n昼間はずっと毛布の上でごろごろしている。\nずっといる pic.twitter.com/TOvtXOUp2F\n\u0026mdash; ずみし (@uzimihsr) October 20, 2022 ひなたぼっこが気持ちよさそう。\nかぁ〜っ pic.twitter.com/bvE24ZiB0C\n\u0026mdash; ずみし (@uzimihsr) October 28, 2022 以前とは違って、毛布の上で寝落ちするようになった。\nたまらなくかわいい。\nこんなに好きな毛布だけど、上からかぶせると嫌がる。\n毛布より軽いブランケットを掛けても逃げるので、たぶん何かこだわりがある。\n圧迫感があるのかな？\nブランケットの上はすき\n被るのはだめ pic.twitter.com/PahXiRgWFL\n\u0026mdash; ずみし (@uzimihsr) October 21, 2022 ちなみに日が出てない日も毛布の上で転がって外を眺めている。\n楽しそうでなにより。\nトッピング 最近の我が家のルールとして、\n朝昼のカリカリを食べた量に応じて晩ごはんのトッピングのレベルが変わる報酬制が採用されている。\n(そとちゃんがあまりにもカリカリを残すので俺が勝手に制定した)\nこのため、カリカリをガッツリ残した日は晩ごはんの猫缶が素の状態で出てくる。\n晩ごはんにトッピングが無くて固まるねこ\n(今日はカリカリ爆残しなのでトッピングなし) pic.twitter.com/MjDE9UH051\n\u0026mdash; ずみし (@uzimihsr) October 12, 2022 トッピングがないと露骨に食いつきが悪くなり、目つきも悪くなる。\n今日もトッピングなしです pic.twitter.com/9hMjBbSKdU\n\u0026mdash; ずみし (@uzimihsr) October 14, 2022 その代わりカリカリを半分くらい食べた日はかつお節がかかったり、\n完食した日はなんとちゅーるがトッピングされる。\nおいしそう。\nでも次の日はまたカリカリをガッツリ残してトッピング無しに戻ることもしばしば。\nたぶんまだこの因果関係を理解していない。\nいつかそとちゃんがこれに気づいてカリカリを毎日完食してくれるといいなと思う。\nハロウィン 気づいたらハロウィンだったので今年もそとちゃんに仮装してもらった。\nドラキュラねこ#HappyHalloween pic.twitter.com/VvYsHk6jWW\n\u0026mdash; ずみし (@uzimihsr) October 31, 2022 例年通りドラキュラマント。\nかわいい。\n去年のこうもりポンチョはあんまり気に入ってくれなかったけど、\nやっぱりこのマントは平気らしい。\nマントつけたまま部屋の中をうろうろしたり、あまり気にならないようだった。\n俺のわがままにつきあってくれたので、\nもちろんごほうびをあげた。\n来年もやろう\u0026hellip;\nおわり 今年もあっという間に10月が終わってしまった。\nとはいえそとちゃんも俺も特に変わらず毎日平和にごろごろしている。\nたのしい。\n今は毛布で耐えていられるけど、本格的に寒くなったらそろそろ\u0026quot;アレ\u0026quot;の出番が来るかもしれない\u0026hellip;\nおまけ ","date":"2022-11-09T00:00:00Z","image":"/post/2022-11-09-sotochan/sotochan.jpeg","permalink":"/post/2022-11-09-sotochan/","title":"10月のそとちゃん(2022)"},{"content":"まとめ 病院 お散歩 窓際族 病院 年に1回のワクチンを打つため、久しぶりに病院に行った。\nキャリーにはすんなり入るそとちゃん。\nお出かけします pic.twitter.com/jv2XlTOjq7\n\u0026mdash; ずみし (@uzimihsr) September 17, 2022 この日は雨だったので車移動。\nキャリーケースに入れられて車の中だとあんまり外の景色が見られなくてつまらなさそうだった。\n病院についてすぐに診察。\n体重は4.04kg。\n前回行ったときに聞き忘れたごはんの量は体重が4kgくらいをキープできてるならこのままでいいらしい。\nちょっと不安なのは歯石が気になると言われたこと。\n去年歯肉炎の兆候があったときは塗り薬を処方してもらったり、\nそれからも毎晩できる範囲で(めっちゃ嫌がるが)歯みがきをしてきたものの、\nあまり良くなってないようだった。\n今の状態だと全身麻酔をして歯石のクリーニングをするしかないらしい。\n全身麻酔は昨年末の手術で一度経験しているので大丈夫そうだが、\nこの日は去年歯を診てくれた先生と違う若手の先生で割とアグレッシブにおすすめしてきたので、\n勢いで決めるのも怖いと思ってこの日は手術の申し込みをしなかった。\n肝心のワクチンもしっかり打ってもらった。\n前回と全く同じでおしりに体温計を入れるときに一番暴れて(体温は37.6℃)、\n注射のときだけおとなしかった(なんで？)。\n診察が終わってすぐ帰れると思ったらちょうど受付が混み出す時間帯にぶつかってしまい、\n会計が出るまで待合室で待つことになった。\nいつもそうだけど病院の待合室はキャリーに入ってない犬がいっぱいいて、\n俺は犬がダメなのでかなりこわかった。\nそとちゃんはやっぱり他の犬猫には興味ないみたいで、\nいつも通りキャリーの中でごろごろしていた。\n\u0026hellip;というわけでもなく、この日はかなり興奮した子犬が1頭暴れ回っていて、\n流石にちょっと驚いていた。\n以降は特に何もなくさっさと帰宅。\n病院に行くときはいつも診察室を探検して楽しそうなのに、\nこの日は混んでたこともあり診察が終わってすぐキャリーに入れられてしまったそとちゃん。\n帰宅してからもなんか元気がなくて少しかわいそうだった。\n(その後起きておやつをガッツリ食べた)\nつかれた pic.twitter.com/JD1qG6EgY4\n\u0026mdash; ずみし (@uzimihsr) September 18, 2022 お散歩 せっかくのおでかけ(病院)がつまんなくてかわいそうだったのと、\nなんか最近ドアの前で鳴くことが多かったので、\n気分転換に家の周りを何回かお散歩してみた。\nとはいえそとちゃんはハーネスをつけられると動かなくなってしまうので、\nはじめは抱っこしてお散歩。\nこれだと数分で腕が疲れてくるし、\n何より俺の両手が塞がってちょっと危ないので別の方法を考えた。\n散歩した pic.twitter.com/IsV4NaWq6T\n\u0026mdash; ずみし (@uzimihsr) September 25, 2022 ↑の写真のようにハーネスをつけた状態で昔使ってたリュック型のキャリーに入れ、\nキャリーの窓を開けてみた。\nこれだと両手も使えるし、そとちゃんも楽な姿勢で外の景色を堪能できていい感じ。\n散歩してみて気づいたのだが新居の周りは犬を散歩させてる人が結構いて、\nねこの散歩は珍しいようで何度か話しかけられた。\nそとちゃんは人見知りしないので、\n知らない人に話しかけられてもちゃんとお返事をしていい子だった。\nその人が連れてる犬のことはガン無視していた。\n窓際族 引越してから約1ヶ月経ち、\nそとちゃんもちょっとずつ新居の良さ(?)がわかってきた様子。\n特に日当たりの良い窓際がいい感じ。\nひなたぼっこ pic.twitter.com/Dw4PJ5J3Sc\n\u0026mdash; ずみし (@uzimihsr) September 21, 2022 天気の良い日は高確率で窓際にいる。\nねてる pic.twitter.com/h3Dy8BHqLC\n\u0026mdash; ずみし (@uzimihsr) September 28, 2022 西日をガッツリ浴びて楽しそう。\nあとたまにベランダ(正確には向かいのお家の屋上)にカラスがくるのがアツいっぽい。\nねこの天敵だから怖がるかと思ったけど、\nそとちゃんは自分が安全な所にいるのがわかっているから(?)いつも面白そうにみている。\nちなみにクラッキングはしない。\nベランダに鳥が来た pic.twitter.com/dM1QGdKhRQ\n\u0026mdash; ずみし (@uzimihsr) September 28, 2022 はじめはなかなかひなたぼっこしてくれなかったりして不安だったが、\n今は毎日楽しそうなので、やっぱり引っ越してよかった。\nおわり 9月はおでかけする機会が多かった。\nそろそろ涼しくなってきて虫も減るし、散歩の頻度を増やしてもいいかもしれない。\n歯石のクリーニングについては心配なので自分でももう少し情報を集めてから慎重に判断したいと思う。\nおまけ ","date":"2022-10-07T00:00:00Z","image":"/post/2022-10-07-sotochan/sotochan.jpeg","permalink":"/post/2022-10-07-sotochan/","title":"9月のそとちゃん(2022)"},{"content":"Summary To trust a self-signed certificate in a scratch image, copy the certificate at the build stage, update the trusted ca-certificates, and then copy it to the scratch image.\nPrerequisites Docker version 20.10.17, build 100c701 go version go1.18.1 darwin/arm64 nginx:1.23.1 Example Create a self-signed certificate and run a HTTPS server on nginx First, create a self-signed certificate.\nThere are many ways to do this, but I prefer to do it with openssl in Docker.\n$ docker container run --rm -it -v=\u0026#34;$PWD:/workdir\u0026#34; -w=\u0026#34;/workdir\u0026#34; --entrypoint=/bin/bash nginx:1.23.1 root@e08bb1ebe3da:/workdir# openssl version OpenSSL 1.1.1n 15 Mar 2022 root@e08bb1ebe3da:/workdir# openssl genpkey -algorithm RSA -out server-private-key.pem ...................+++++ ......................................................................................................+++++ root@e08bb1ebe3da:/workdir# openssl req -x509 -key server-private-key.pem -out server-cert.pem -addext \u0026#39;subjectAltName = DNS:hogehoge.uzimihsr.com\u0026#39; You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter \u0026#39;.\u0026#39;, the field will be left blank. ----- Country Name (2 letter code) [AU]: State or Province Name (full name) [Some-State]: Locality Name (eg, city) []: Organization Name (eg, company) [Internet Widgits Pty Ltd]: Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []:hogehoge.uzimihsr.com Email Address []: root@e08bb1ebe3da:/workdir# exit # the private key and the self-signed certificates are created on the host $ ls server-cert.pem\tserver-private-key.pem Then start a HTTPS server on nginx.\n$ tree . . ├── docker-compose.yaml ├── https.conf ├── server-cert.pem └── server-private-key.pem 0 directories, 4 files $ docker compose up -d $ docker compose ps NAME COMMAND SERVICE STATUS PORTS hogehoge.uzimihsr.com \u0026#34;/docker-entrypoint.…\u0026#34; nginx running 80/tcp, 0.0.0.0:443-\u0026gt;443/tcp $ curl -I https://hogehoge.uzimihsr.com --cacert ./server-cert.pem --resolve \u0026#34;hogehoge.uzimihsr.com:443:127.0.0.1\u0026#34; HTTP/1.1 200 OK ... Create an application that sends HTTP requests in Go Next, create a simple HTTP status checker in Go.\n$ go run main.go https://example.com 2022/09/28 23:45:32 target: https://example.com, insecure: false 2022/09/28 23:45:33 status: 200 OK Build and run this application with Docker.\nOops! The http-status-checker container failed due to the certificate error.😭\n$ tree . . ├── Dockerfile ├── docker-compose.yaml ├── go.mod ├── https.conf ├── main.go ├── server-cert.pem └── server-private-key.pem 0 directories, 7 files $ cat go.mod module http-status-checker go 1.18 $ docker compose up -d $ docker compose ps NAME COMMAND SERVICE STATUS PORTS hogehoge.uzimihsr.com \u0026#34;/docker-entrypoint.…\u0026#34; nginx running 80/tcp, 0.0.0.0:443-\u0026gt;443/tcp http-status-checker \u0026#34;./app https://hogeh…\u0026#34; http-status-checker exited (1) $ docker compose logs http-status-checker http-status-checker | 2022/09/28 12:17:41 target: https://hogehoge.uzimihsr.com/, insecure: false http-status-checker | 2022/09/28 12:17:41 [ERROR] HTTP(S) request failed: Get \u0026#34;https://hogehoge.uzimihsr.com/\u0026#34;: x509: certificate signed by unknown authority Trust the certificate in a scratch image To avoid the certificate error, the self-signed certificate should be trusted in a scratch image.\n(Of course, it is possible to set tls.Config.InsecureSkipVerify as a workaround, but I have tried to trust the self-signed certificate.)\nSince the golang image is Debian-based, the list of CA certificates can be updated with the update-ca-certificates command.\nThe Dockerfile is rewrited as follows.\nThe following operations are added: copy the certificate we want to trust with .crt extension and run the update-ca-certificates command.\nThis allows /etc/ssl/certs/ca-certificates.crt in the build stage to trust the specified certificate.\nCOPY server-cert.pem /usr/local/share/ca-certificates/server-cert.crt RUN update-ca-certificates Finally, rebuild and restart the container.\nThis time it worked fine with no certificate errors.🎉\n$ docker compose up -d --remove-orphans --build $ docker compose ps NAME COMMAND SERVICE STATUS PORTS hogehoge.uzimihsr.com \u0026#34;/docker-entrypoint.…\u0026#34; nginx running 80/tcp, 0.0.0.0:443-\u0026gt;443/tcp http-status-checker \u0026#34;./app https://hogeh…\u0026#34; http-status-checker exited (0) $ docker compose logs http-status-checker http-status-checker | 2022/09/28 13:44:43 target: https://hogehoge.uzimihsr.com/, insecure: false http-status-checker | 2022/09/28 13:44:43 status: 200 OK ","date":"2022-09-29T00:00:00Z","image":"/post/2022-09-29-golang-scratch-trust-cert/sotochan.jpeg","permalink":"/post/2022-09-29-golang-scratch-trust-cert/","title":"golang: Trust a self-signed certificate in a scratch image"},{"content":"まとめ マルチステージビルドで実行用のバイナリをscratchに載せる場合、\nビルドと同じタイミングで信頼したい証明書を用意して、\nCA証明書のリストを更新したものをアプリと一緒にコピーすればよさそう。\n環境 Docker version 20.10.17, build 100c701 go version go1.18.1 darwin/arm64 nginx:1.23.1 やったこと オレオレ証明書を作成してnginxをHTTPSで建てる まずは検証用にオレオレ証明書を作成する。\nopensslが使えるならどんなやり方でもいいが、自分はDocker上で作ってホストに持ってくるのがすき。\n# ホストOSのボリュームをマウントする $ docker container run --rm -it -v=\u0026#34;$PWD:/workdir\u0026#34; -w=\u0026#34;/workdir\u0026#34; --entrypoint=/bin/bash nginx:1.23.1 root@e08bb1ebe3da:/workdir# openssl version OpenSSL 1.1.1n 15 Mar 2022 # 秘密鍵の作成 root@e08bb1ebe3da:/workdir# openssl genpkey -algorithm RSA -out server-private-key.pem ...................+++++ ......................................................................................................+++++ # オレオレ証明書の作成 root@e08bb1ebe3da:/workdir# openssl req -x509 -key server-private-key.pem -out server-cert.pem -addext \u0026#39;subjectAltName = DNS:hogehoge.uzimihsr.com\u0026#39; You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter \u0026#39;.\u0026#39;, the field will be left blank. ----- Country Name (2 letter code) [AU]: State or Province Name (full name) [Some-State]: Locality Name (eg, city) []: Organization Name (eg, company) [Internet Widgits Pty Ltd]: Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []:hogehoge.uzimihsr.com Email Address []: root@e08bb1ebe3da:/workdir# exit # ホスト上に鍵と証明書が置かれている $ ls server-cert.pem\tserver-private-key.pem 作成した秘密鍵とオレオレ証明書を使ってnginxをHTTPSで立ててみる。\n# ディレクトリの状態 $ tree . . ├── docker-compose.yaml ├── https.conf ├── server-cert.pem └── server-private-key.pem 0 directories, 4 files # nginxを起動 $ docker compose up -d $ docker compose ps NAME COMMAND SERVICE STATUS PORTS hogehoge.uzimihsr.com \u0026#34;/docker-entrypoint.…\u0026#34; nginx running 80/tcp, 0.0.0.0:443-\u0026gt;443/tcp # CA証明書を指定して叩くとHTTPSで通信できている $ curl -I https://hogehoge.uzimihsr.com --cacert ./server-cert.pem --resolve \u0026#34;hogehoge.uzimihsr.com:443:127.0.0.1\u0026#34; HTTP/1.1 200 OK ... HTTPリクエストを送るGoアプリの作成 先ほど立てたnginx(HTTPS)に対してHTTPリクエストを送る簡単なアプリを実装する。\n内容としては引数で指定されたURLに対してHTTP GETしてそのステータスコードを表示する(200系以外は異常終了する)だけのもの。\n$ go run main.go https://example.com 2022/09/28 23:45:32 target: https://example.com, insecure: false 2022/09/28 23:45:33 status: 200 OK これをマルチステージビルドでscratchに乗せて動かしてみる。\n起動したコンテナを確認すると、オレオレ証明書を信頼できずにエラーとなっている。\n$ tree . . ├── Dockerfile ├── docker-compose.yaml ├── go.mod ├── https.conf ├── main.go ├── server-cert.pem └── server-private-key.pem 0 directories, 7 files $ cat go.mod module http-status-checker go 1.18 $ docker compose up -d $ docker compose ps NAME COMMAND SERVICE STATUS PORTS hogehoge.uzimihsr.com \u0026#34;/docker-entrypoint.…\u0026#34; nginx running 80/tcp, 0.0.0.0:443-\u0026gt;443/tcp http-status-checker \u0026#34;./app https://hogeh…\u0026#34; http-status-checker exited (1) $ docker compose logs http-status-checker http-status-checker | 2022/09/28 12:17:41 target: https://hogehoge.uzimihsr.com/, insecure: false http-status-checker | 2022/09/28 12:17:41 [ERROR] HTTP(S) request failed: Get \u0026#34;https://hogehoge.uzimihsr.com/\u0026#34;: x509: certificate signed by unknown authority オレオレ証明書を信頼する こんなとき、実装にもあるようにtls.Config.InsecureSkipVerifyを指定することで証明書エラーを回避することは可能だが、\nどうしてもオレオレ証明書を信頼する設定で動かしたくなった。\nどうしたものかと悩んだが、単純にupdate-ca-certificatesで信頼した証明書をコピーする方法に落ち着いた。\nDockerfileの設定を次のように書き換える。\n変更点は次の部分。\n信頼したい証明書を/usr/local/share/ca-certificates/配下に.crtという名前でコピーして、update-ca-certificatesを実行している。\nこれにより、ビルド用コンテナ内の/etc/ssl/certs/ca-certificates.crtが更新されて対象の証明書を信頼できるようになる。\nCOPY server-cert.pem /usr/local/share/ca-certificates/server-cert.crt RUN update-ca-certificates 再度実行してみると、今度はオレオレ証明書が信頼できていてエラーが発生しなくなった。\n$ docker compose up -d --remove-orphans --build $ docker compose ps NAME COMMAND SERVICE STATUS PORTS hogehoge.uzimihsr.com \u0026#34;/docker-entrypoint.…\u0026#34; nginx running 80/tcp, 0.0.0.0:443-\u0026gt;443/tcp http-status-checker \u0026#34;./app https://hogeh…\u0026#34; http-status-checker exited (0) $ docker compose logs http-status-checker http-status-checker | 2022/09/28 13:44:43 target: https://hogehoge.uzimihsr.com/, insecure: false http-status-checker | 2022/09/28 13:44:43 status: 200 OK 今回はgolangのimageがdebian系だったのでこの手順で試したが、\nRed Hat系なら同様に.crtファイルを/usr/share/pki/ca-trust-source/anchors/配下にコピーして、\nupdate-ca-trustすると/etc/pki/tls/certs/ca-bundle.crtが更新されたはず。(試してない)\nおわり 久しぶりにGo+scratchでimageを作ったときに詰まったのでおさらいした。\nこんなことしてる暇があったらdistrolessに移行しろと言われればそれはそう\u0026hellip;\nおまけ ","date":"2022-09-29T00:00:00Z","image":"/post/2022-09-29-golang-scratch-trust-cert-ja/sotochan.jpeg","permalink":"/post/2022-09-29-golang-scratch-trust-cert-ja/","title":"scratchイメージ上のGoで任意の証明書を信頼する"},{"content":"まとめ 引越し ひなたぼっこ 袋 引越し 色々あってまた家を引っ越すことになった。\n今回も荷造りを邪魔する手伝うそとちゃん。\n布団袋(かなりでかい)に興味津々。\n段ボールの調査も抜かりない。\n新居到着後は早速探検。\nそとちゃんは新しい場所が大好きなので速攻で慣れてくれて助かった。\nというかむしろ大興奮で暴れまわり荷解きをめちゃくちゃ邪魔した\nひなたぼっこ 今回引越しを決断した一番の理由は前の家の日当たりの悪さだった。\n全く日が入らないわけではないが日照時間が極端に短いので、\n前の家では抱っこ状態で外に出して陽に当てていた。\nそれだと昼に毎回抱っこして外まで行くのが面倒だったのと、\nやっぱり自分のタイミングで好きなようにひなたぼっこさせてあげたいと思い引越しを決断。\n(そとちゃん的に抱っこは好きでも嫌いでもなさそうだが)\n新居の日当たりはそこそこ良く、\n昼間は部屋の中にしっかり陽が入ってくる。\n\u0026hellip;のだが、引越して数日間はそとちゃんがなかなか自分から陽に当たらず\u0026hellip;\nこの2年弱の間抱っこされてひなたぼっこするのが当たり前になっていて、\n自分でひなたぼっこすることを忘れてしまっていたのかも？\nせっかく引越したのにこれじゃあんまりなので、\n陽が入るたびにそとちゃんを窓際に運ぶのを数日繰り返したところ(低頻度だが)自分でもひなたぼっこしてくれるようになった。\nとはいえ陽に当たり過ぎても良くないと思うので、適度に楽しんでもらいたい。\n袋 やっぱり袋が好き。\n袋 pic.twitter.com/ArcBAlCQ8W\n\u0026mdash; ずみし (@uzimihsr) August 22, 2022 最近は特にニトリの大きいサイズのレジ袋に入って暴れるのが好き。\n袋の中で動く→カサカサ音がする→獲物と勘違いして追っかけ回す→カサカサ音がする をひたすら繰り返す。\nあとは王道(そとちゃん基準)だけど袋の中に投げ入れたおもちゃをひたすら外からしばくのも楽しいらしい。\n引越し後にニトリで買うものがちょいちょいあったので、さらに新しい袋が補給された。\nこれで当分は耐えられそう。\nおわり 2度目の引越しを終えて、やっぱりそとちゃんは環境の変化に強いなと感じた。\n本当はストレスになっているのかもしれないが、\n病院に行った時の様子とかを見ると肝が据わっているというか単純に普段と違う場所に行くのが好きなんだと思う。\n何はともあれ無事に引っ越しできてよかった。\nおまけ ","date":"2022-09-07T00:00:00Z","image":"/post/2022-09-07-sotochan/sotochan.jpeg","permalink":"/post/2022-09-07-sotochan/","title":"8月のそとちゃん(2022)"},{"content":"まとめ ごろごろ お風呂 カリカリ ごろごろ いつものことなんだけどそとちゃんはよくごろごろしている。\n転がってる pic.twitter.com/PFpU1soXb8\n\u0026mdash; ずみし (@uzimihsr) July 4, 2022 遊び疲れてごろごろするときもあるけど、\nなにもなくただ床に落ちていることが多い。\n落ちてる pic.twitter.com/vTzBUMGNGY\n\u0026mdash; ずみし (@uzimihsr) July 12, 2022 あとはなんとなく左側を下にして落ちてる傾向がある気がする。\n落ちてる時におなかを突っつくとよく転がって面白い。\n踊ってる pic.twitter.com/GiUotnoYNF\n\u0026mdash; ずみし (@uzimihsr) July 28, 2022 たまに椅子の下とかドアの前で転がってて危ないのでやめてほしい。😅\n椅子を動かせなくする技 pic.twitter.com/sBhW26qLrT\n\u0026mdash; ずみし (@uzimihsr) July 21, 2022 お風呂 流石に抜け毛が多くなってきたので、約1年ぶりのお風呂に入ってもらった。🛁\n前回はシャワーだけだったけど、\n今回はしっかり洗って抜け毛を流すためにお風呂(バスタブ)に挑戦。\nシャワーは耐えられるそとちゃんでもバスタブにはかなりビビっていた。\n顔の近くにお湯があるのが怖いみたいで、若干パニックになっていてかわいそうだった。\n(が、シャンプーのためにちゃんと濡らした😂)\n脱走を繰り返しながらもバスタブでしっかり毛を濡らして、ようやくシャンプー。\nみゃおみゃお文句言いながらもなんとか耐えてくれた。えらい。\n最後にもう1回お風呂に浸かってしっかり抜け毛を流してもらった。\nそとちゃんの「もう終わりだと思ってたのに\u0026hellip;なんでや\u0026hellip;」みたいな顔がかわいそうだけど面白くてちょっと笑ってしまった。\nあとは乾かして今度こそお風呂は終わり。\nここまでされても噛んだり引っ掻いたり「シャーッ」とか言わないのでそとちゃんは本当に優しい。\n大変な思いをしてかわいそうだったけど、\n毛はフワッフワになったし毛玉も吐かなくなったので良かった。\nカリカリ そとちゃんがカリカリをちょっとだけ食べるようになった。\n(ちょっとかわいそうだけど)猫缶をあげるときのトッピングとか夜のおやつを少なめにしたら、\n流石にお腹が空いたみたいで次の日から朝と昼のカリカリを渋々食べてくれた。\nまだ完食とまではいかないけど、全然食べなかった時期に比べるとだいぶ進歩した。\n完食したらもっとえらい\u0026hellip;\nおわり 7月のそとちゃんは水責めに遭ったりおやつを減らされたりで散々だった。\nそれでいじけることもあるけど、おしりを叩いたり撫でたりすればすぐごきげんになってしまうので助かる。\n単純でかわいい。えらい。\nおまけ ","date":"2022-08-06T00:00:00Z","image":"/post/2022-08-06-sotochan/sotochan.jpeg","permalink":"/post/2022-08-06-sotochan/","title":"7月のそとちゃん(2022)"},{"content":"まとめ またたび キャリーケース 吐く カリカリ食べない またたび 新しいおもちゃを買った。\nCAT CHAP ペンタゴン\nそとちゃんは結構気に入ってくれたみたいでゴロゴロ転がして遊んでくれた。\nせっかくなので動画を撮ってみた。\n以前から俺がヤベーと思っていたそとちゃんのまたたびのキメ方をようやく映像に残すことができた。🌿\n動画にも映ってるが実は転がして遊ぶのは本当に最初だけで、\n一度動きを止めるコツを掴んだらあとは押さえてひたすら舐めてヤバい成分を摂取し続ける。\nまたたびジャンキーのそとちゃんは耐性がついてしまっているようで、\n10分程度キメ続けてようやくトリップ。🥴\n最後は謎のうねうね踊り(かわいい)を見せてから数十秒で寝落ちしてしまった。\n満足してくれたようでよかった。\nキャリーケース ちょっと前まで地震が多かったのもあって、\n万が一に備えて大きめのキャリーケースを買ってみた。\nキャンピングキャリー 折りたたみ\n梱包を開ける前からそとちゃんは興味津々で、\n廊下に置いたらすぐに入ってくれた。\n実物を見ずに通販で買ったので大きさが不安だったが、\n4kgくらいのそとちゃんには十分だった。\n両サイドの扉が取り外しできたのでいい感じのお部屋になり、\n居間が暑い日なんかはよくここで涼んでいる。\nつかれた pic.twitter.com/qWwPk5p4F3\n\u0026mdash; ずみし (@uzimihsr) June 4, 2022 ちなみにダンボールトンネル(ぼろぼろ)と組み合わせて一気に走り抜けるのが流行っている。\n吐く 6月はなんかめっちゃ吐いた。\n吐いた pic.twitter.com/KUyEWnres8\n\u0026mdash; ずみし (@uzimihsr) June 10, 2022 吐いた pic.twitter.com/UyW8EwZjzL\n\u0026mdash; ずみし (@uzimihsr) June 19, 2022 今までは2,3ヶ月に1回吐くか吐かないかだったのに、\n6月は結構なペースで(3回くらい)吐いてしまった。\n今月はおやつのにぼしをなぜか焦って飲み込んですぐにオエって吐き出したのと、\nブラッシングの後にめちゃ毛繕いして毛玉を吐いたのが数回。\n前者はゆっくり食べれば防げるので今後あげるときに気をつけたい。\n毛玉の方はブラッシングを頑張って抜け毛を減らすしかないが、\nそとちゃんはブラシ嫌い(週に1回30分が限界)な上にブラシするとめちゃめちゃ毛繕いするのでどうしようもない\u0026hellip;\nカリカリ食べない そとちゃんがまたカリカリを残すようになってしまった。😭\nこの状況で「わたしはごはんをもらえていません」みたいな顔しないでほしい\n(このあと猫缶をあげました) pic.twitter.com/foZzpmEMeJ\n\u0026mdash; ずみし (@uzimihsr) June 20, 2022 おそらく湿気が高い日に食べたカリカリが若干傷んでいて、\nそれが美味しくなかったからそれ以降全然食べなくなってしまったのだと思う。\n自動給餌器の中に乾燥剤を取り付けて湿気対策はしたものの、\nそこから出たカリカリをそとちゃんが食べずに放置するとそれがまた湿気ってさらに食べない\u0026hellip;という負のループに😭😭😭\n特に湿気がひどい日には自動給餌器を使わずに手でカリカリをあげるんだけど、\nそれでも一度不味いと判断したものはなかなか食べてくれない。\n過去にカリカリを食べなかった時期とは違って、\n俺が部屋にいない時ですら食べないので本当に困ってしまった。\n食欲出すためにおやつをトッピングするとそこだけ舐め取ってしまうし、\n万策尽きた\u0026hellip;\nどうすりゃいいんだ\u0026hellip;😭\nおわり なんか色々忙しくて書くのがバチクソ遅くなってしまった。\n来月こそは溜めずにすぐ書きたい。\nおまけ ","date":"2022-07-22T00:00:00Z","image":"/post/2022-07-22-sotochan/sotochan.jpeg","permalink":"/post/2022-07-22-sotochan/","title":"6月のそとちゃん(2022)"},{"content":"まとめ 6さい 記念写真 6さい そとちゃんがたぶん6さいになった。🎉\n去年は奮発して買ったねこ用ケーキをめちゃくちゃ残したので、\n今年はかなり小さめのタルトでお祝いした。\nたぶん6さいになりました🎂 pic.twitter.com/oyBvP8ogSW\n\u0026mdash; ずみし (@uzimihsr) May 1, 2022 今年は表面のクリームをちょっとだけ舐めて、あとは一度も手をつけなかった。😭\n仕方がないのでトッピングマシマシの特製ごはんでお祝い。\nこちらはものすごい勢いで完食。\nやっぱり甘いものよりしょっぱいものの方がいいみたい。\nちゅーるマシマシごはん pic.twitter.com/GLChnmvNyx\n\u0026mdash; ずみし (@uzimihsr) May 1, 2022 誕生日プレゼントは動くえびのおもちゃ。\n結構おもしろそうだが\u0026hellip;?\n残念😭😭😭\nでもそとちゃんがちょっとだけ嬉しそうだったのでヨシ！\n満足 pic.twitter.com/36VmfBEVqk\n\u0026mdash; ずみし (@uzimihsr) May 1, 2022 記念写真 今年も去年と同じ写真屋さんに記念写真を撮りに行った。📷\nそとちゃんは今年も全然おとなしくできなかった。😂\n隣のスタジオ(部屋は繋がっている)で撮ってた人が気になってしまい、\nずっと目で追いかけていた。\n入っちゃいけないところを探検したりなかなかカメラの方を向いてくれなかったりで撮影は本当に大変だった。\n気が済むまで遊ばせたりしてなんとか最低限の枚数は撮れたが、\n今度は疲れて集中力が無くなってしまったのでそれ以上はあまり撮れなかった。\n俺とカメラマンさんは大変な思いをしたけどそとちゃんは楽しめたみたいなのでよかった(?)。\nまた来年も撮りに行きたい。\nおわり そとちゃんと暮らし始めてからもう3年経つことに驚いた。\nあっという間だった。\n初めて会った時から割と懐いてくれてたけど、\nこの3年でさらに仲良くなれたように思う。\n(ちなみに一緒にベッドで寝たのは数回しかない。ガードが固い)\nついでにふてぶてしさが日に日に増している気がするが、\nねこは幸せになるほどそうなっていくものだと信じたい\u0026hellip;\nおまけ ","date":"2022-06-14T00:00:00Z","image":"/post/2022-06-14-sotochan/sotochan.jpg","permalink":"/post/2022-06-14-sotochan/","title":"5月のそとちゃん(2022)"},{"content":"日本語/Japanese\nSummary This is a note on setting up the terminal on my new MacBook.\nPrerequisites MacBook Pro 16-inch 2021 macOS Monterey 12.2.1 Details Install Homebrew Install zsh plugins Configure PROMPT Install iterm2 Install Homebrew Since Catalina, the default login shell for Mac is zsh.\nuzimihsr@MacBook ~ % echo $SHELL /bin/zsh uzimihsr@MacBook ~ % zsh --version zsh 5.8 (x86_64-apple-darwin21.0) At first, install Homebrew.\nOpen https://brew.sh and copy and paste the command in the terminal.\nuzimihsr@MacBook ~ % /bin/bash -c \u0026#34;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\u0026#34; ... Warning: /opt/homebrew/bin is not in your PATH. Instructions on how to configure your shell for Homebrew can be found in the \u0026#39;Next steps\u0026#39; section below. ==\u0026gt; Installation successful! ... ==\u0026gt; Next steps: - Run these two commands in your terminal to add Homebrew to your PATH: echo \u0026#39;eval \u0026#34;$(/opt/homebrew/bin/brew shellenv)\u0026#34;\u0026#39; \u0026gt;\u0026gt; /Users/uzimihsr/.zprofile eval \u0026#34;$(/opt/homebrew/bin/brew shellenv)\u0026#34; - Run brew help to get started - Further documentation: https://docs.brew.sh Follow the instructions to update the PATH.\nuzimihsr@MacBook ~ % echo \u0026#39;eval \u0026#34;$(/opt/homebrew/bin/brew shellenv)\u0026#34;\u0026#39; \u0026gt;\u0026gt; /Users/$USER/.zprofile uzimihsr@MacBook ~ % eval \u0026#34;$(/opt/homebrew/bin/brew shellenv)\u0026#34; uzimihsr@MacBook ~ % brew doctor Your system is ready to brew. uzimihsr@MacBook ~ % which brew /opt/homebrew/bin/brew uzimihsr@MacBook ~ % brew --version Homebrew 3.4.10 Homebrew/homebrew-core (git revision 239cad746ef; last commit 2022-05-03) Good.\nNow I can use the brew command.\nInstall zsh plugins Next, install the following zsh plugins with brew.\nzsh-autosuggestions zsh-completions zsh-syntax-highlighting uzimihsr@MacBook ~ % brew install zsh-autosuggestions ... ==\u0026gt; Caveats To activate the autosuggestions, add the following at the end of your .zshrc: source /opt/homebrew/share/zsh-autosuggestions/zsh-autosuggestions.zsh You will also need to restart your terminal for this change to take effect. ... uzimihsr@MacBook ~ % brew install zsh-completions ... ==\u0026gt; Caveats To activate these completions, add the following to your .zshrc: if type brew \u0026amp;\u0026gt;/dev/null; then FPATH=$(brew --prefix)/share/zsh-completions:$FPATH autoload -Uz compinit compinit fi You may also need to force rebuild `zcompdump`: rm -f ~/.zcompdump; compinit Additionally, if you receive \u0026#34;zsh compinit: insecure directories\u0026#34; warnings when attempting to load these completions, you may need to run this: chmod -R go-w \u0026#39;/opt/homebrew/share/zsh\u0026#39; ... uzimihsr@MacBook ~ % brew install zsh-syntax-highlighting ... ==\u0026gt; Caveats To activate the syntax highlighting, add the following at the end of your .zshrc: source /opt/homebrew/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh If you receive \u0026#34;highlighters directory not found\u0026#34; error message, you may need to add the following to your .zshenv: export ZSH_HIGHLIGHT_HIGHLIGHTERS_DIR=/opt/homebrew/share/zsh-syntax-highlighting/highlighters ... Follow the instructions to create ~/.zshrc, then restart the terminal.\nuzimihsr@MacBook ~ % cat ~/.zshrc # zsh-autosuggestions source /opt/homebrew/share/zsh-autosuggestions/zsh-autosuggestions.zsh # zsh-completions if type brew \u0026amp;\u0026gt;/dev/null; then FPATH=$(brew --prefix)/share/zsh-completions:$FPATH autoload -Uz compinit compinit fi # zsh-syntax-highlighting source /opt/homebrew/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh Follow the instructions on zsh-completions to rebuild zcompdump.\nThe \u0026ldquo;zsh compinit: insecure directories\u0026rdquo; error did not go away with the command as instructed, so I was a bit worn out.\nuzimihsr@MacBook ~ % rm -f ~/.zcompdump; compinit zsh compinit: insecure directories, run compaudit for list. Ignore insecure directories and continue [y] or abort compinit [n]? y uzimihsr@MacBook ~ % chmod -R go-w \u0026#39;/opt/homebrew/share/zsh\u0026#39; uzimihsr@MacBook ~ % rm -f ~/.zcompdump; compinit zsh compinit: insecure directories, run compaudit for list. Ignore insecure directories and continue [y] or abort compinit [n]? y uzimihsr@MacBook ~ % compaudit There are insecure directories: /opt/homebrew/share uzimihsr@MacBook ~ % chmod -R go-w \u0026#39;/opt/homebrew/share\u0026#39; uzimihsr@MacBook ~ % rm -f ~/.zcompdump; compinit Restart the terminal again, and all plugins are enabled.\nConfigure PROMPT Set up the PROMPT to be more convenient.\nMy PROMPT shows the current directory and a newlined $.\nThe Z Shell Manual has more details on the variables that can be used with the PROMPT.\nuzimihsr@MacBook ~ % PROMPT=\u0026#34;\u0026#34;$\u0026#39;\\n\u0026#39;\u0026#34;%d\u0026#34;$\u0026#39;\\n\u0026#39;\u0026#34;$ \u0026#34; /Users/uzimihsr $ Finally, update ~/.zshrc.\n$ cat ~/.zshrc PROMPT=\u0026#34;\u0026#34;$\u0026#39;\\n\u0026#39;\u0026#34;%d\u0026#34;$\u0026#39;\\n\u0026#39;\u0026#34;$ \u0026#34; ... Install iterm2 Install iterm2 with homebrew cask.\n$ brew install --cask iterm2 ==\u0026gt; Tapping homebrew/cask Cloning into \u0026#39;/opt/homebrew/Library/Taps/homebrew/homebrew-cask\u0026#39;... remote: Enumerating objects: 635835, done. remote: Counting objects: 100% (9/9), done. remote: Compressing objects: 100% (5/5), done. remote: Total 635835 (delta 4), reused 9 (delta 4), pack-reused 635826 Receiving objects: 100% (635835/635835), 299.65 MiB | 4.39 MiB/s, done. Resolving deltas: 100% (450073/450073), done. Tapped 3994 casks (4,066 files, 320.0MB). ==\u0026gt; Downloading https://iterm2.com/downloads/stable/iTerm2-3_4_15.zip ######################################################################## 100.0% ==\u0026gt; Installing Cask iterm2 ==\u0026gt; Moving App \u0026#39;iTerm.app\u0026#39; to \u0026#39;/Applications/iTerm.app\u0026#39; 🍺 iterm2 was successfully installed! I like to double-tap Control(⌃) to show and hide the terminal with Hotkeys.\nOpen \u0026ldquo;Preference(⌘,)\u0026quot;→\u0026ldquo;Keys\u0026rdquo;→\u0026ldquo;Hotkey\u0026rdquo;→\u0026ldquo;Create a Dedicated Hotkey window\u0026hellip;\u0026rdquo;.\nSet up the Hotkey window as shown in the images below.\nIn addition, go to \u0026ldquo;Advanced\u0026rdquo;→\u0026ldquo;Open a new window when you click the dock icon\u0026hellip;\u0026rdquo; and specify \u0026ldquo;No\u0026rdquo; to prevent opening a new terminal window except with a hotkey.\nRestart iterm2 and hit Control(⌃) repeatedly and the terminal will flap open and close.\n(The following is the same content in Japanese.)\nまとめ MacBookを買い換えたので、ターミナル周りの設定をした。\nあんまりやる機会もないけど一応自分用にログを残しておく。\n環境 MacBook Pro 16-inch 2021 macOS Monterey 12.2.1 やったこと Homebrewのインストール zshプラグインのインストール プロンプトをいじる iterm2のインストール Homebrewのインストール 新しいMacが起動したのでまずはシェルをzshに\u0026hellip;\nと思ったらだいぶ前(Catalina?)からデフォルトでシェルがzshになっていたらしい。\nuzimihsr@MacBook ~ % echo $SHELL /bin/zsh uzimihsr@MacBook ~ % zsh --version zsh 5.8 (x86_64-apple-darwin21.0) zshのプラグインを入れたいし、後でいろんなものを入れるのに使うのでまずはbrewが使えるようにする。\nターミナルを開き、https://brew.shからコマンドをコピーしてきて貼り付け。\nuzimihsr@MacBook ~ % /bin/bash -c \u0026#34;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\u0026#34; ... Warning: /opt/homebrew/bin is not in your PATH. Instructions on how to configure your shell for Homebrew can be found in the \u0026#39;Next steps\u0026#39; section below. ==\u0026gt; Installation successful! ... ==\u0026gt; Next steps: - Run these two commands in your terminal to add Homebrew to your PATH: echo \u0026#39;eval \u0026#34;$(/opt/homebrew/bin/brew shellenv)\u0026#34;\u0026#39; \u0026gt;\u0026gt; /Users/uzimihsr/.zprofile eval \u0026#34;$(/opt/homebrew/bin/brew shellenv)\u0026#34; - Run brew help to get started - Further documentation: https://docs.brew.sh 途中でPATHを通せ的な指示があったのでそれに従い、動作を確認する。\nuzimihsr@MacBook ~ % echo \u0026#39;eval \u0026#34;$(/opt/homebrew/bin/brew shellenv)\u0026#34;\u0026#39; \u0026gt;\u0026gt; /Users/$USER/.zprofile uzimihsr@MacBook ~ % eval \u0026#34;$(/opt/homebrew/bin/brew shellenv)\u0026#34; uzimihsr@MacBook ~ % brew doctor Your system is ready to brew. uzimihsr@MacBook ~ % which brew /opt/homebrew/bin/brew uzimihsr@MacBook ~ % brew --version Homebrew 3.4.10 Homebrew/homebrew-core (git revision 239cad746ef; last commit 2022-05-03) これでbrewが使えるようになった。\nzshプラグインのインストール zshを快適に使うために下記のプラグインをbrewで入れる。\nzsh-autosuggestions zsh-completions zsh-syntax-highlighting uzimihsr@MacBook ~ % brew install zsh-autosuggestions ... ==\u0026gt; Caveats To activate the autosuggestions, add the following at the end of your .zshrc: source /opt/homebrew/share/zsh-autosuggestions/zsh-autosuggestions.zsh You will also need to restart your terminal for this change to take effect. ... uzimihsr@MacBook ~ % brew install zsh-completions ... ==\u0026gt; Caveats To activate these completions, add the following to your .zshrc: if type brew \u0026amp;\u0026gt;/dev/null; then FPATH=$(brew --prefix)/share/zsh-completions:$FPATH autoload -Uz compinit compinit fi You may also need to force rebuild `zcompdump`: rm -f ~/.zcompdump; compinit Additionally, if you receive \u0026#34;zsh compinit: insecure directories\u0026#34; warnings when attempting to load these completions, you may need to run this: chmod -R go-w \u0026#39;/opt/homebrew/share/zsh\u0026#39; ... uzimihsr@MacBook ~ % brew install zsh-syntax-highlighting ... ==\u0026gt; Caveats To activate the syntax highlighting, add the following at the end of your .zshrc: source /opt/homebrew/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh If you receive \u0026#34;highlighters directory not found\u0026#34; error message, you may need to add the following to your .zshenv: export ZSH_HIGHLIGHT_HIGHLIGHTERS_DIR=/opt/homebrew/share/zsh-syntax-highlighting/highlighters ... インストール時の指示に従い、~/.zshrcを作成する。\nuzimihsr@MacBook ~ % cat ~/.zshrc # zsh-autosuggestions source /opt/homebrew/share/zsh-autosuggestions/zsh-autosuggestions.zsh # zsh-completions if type brew \u0026amp;\u0026gt;/dev/null; then FPATH=$(brew --prefix)/share/zsh-completions:$FPATH autoload -Uz compinit compinit fi # zsh-syntax-highlighting source /opt/homebrew/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh ここで一旦ターミナルを閉じてから再度起動する。\nzsh-completionsのインストール時に指示された通り、zcompdumpを再作成する。\n指示された通りのコマンドでは\u0026quot;zsh compinit: insecure directories\u0026quot;のエラーが消えなかったのでちょっと消耗した。\nuzimihsr@MacBook ~ % rm -f ~/.zcompdump; compinit zsh compinit: insecure directories, run compaudit for list. Ignore insecure directories and continue [y] or abort compinit [n]? y uzimihsr@MacBook ~ % chmod -R go-w \u0026#39;/opt/homebrew/share/zsh\u0026#39; uzimihsr@MacBook ~ % rm -f ~/.zcompdump; compinit zsh compinit: insecure directories, run compaudit for list. Ignore insecure directories and continue [y] or abort compinit [n]? y uzimihsr@MacBook ~ % compaudit There are insecure directories: /opt/homebrew/share uzimihsr@MacBook ~ % chmod -R go-w \u0026#39;/opt/homebrew/share\u0026#39; uzimihsr@MacBook ~ % rm -f ~/.zcompdump; compinit もう一度再起動すると今回追加したプラグインが全て有効になっている。\nプロンプトをいじる 初期設定のプロンプトだと使いづらいのでここでいじっておく。\nPROMPTで使える変数についてはThe Z Shell Manualが詳しい。\n自分は割とシンプルなのが好きで、\n直前のコマンド実行結果から改行されてカレントディレクトリが表示できていればそれでいい。\nuzimihsr@MacBook ~ % PROMPT=\u0026#34;\u0026#34;$\u0026#39;\\n\u0026#39;\u0026#34;%d\u0026#34;$\u0026#39;\\n\u0026#39;\u0026#34;$ \u0026#34; /Users/uzimihsr $ 色々試して気に入ったものができたので、~/.zshrcに書いておく。\n$ cat ~/.zshrc PROMPT=\u0026#34;\u0026#34;$\u0026#39;\\n\u0026#39;\u0026#34;%d\u0026#34;$\u0026#39;\\n\u0026#39;\u0026#34;$ \u0026#34; ... iterm2のインストール ターミナルも標準のままだと不便なので、\nhomebrew caskを使ってiterm2をインストールする。\n$ brew install --cask iterm2 ==\u0026gt; Tapping homebrew/cask Cloning into \u0026#39;/opt/homebrew/Library/Taps/homebrew/homebrew-cask\u0026#39;... remote: Enumerating objects: 635835, done. remote: Counting objects: 100% (9/9), done. remote: Compressing objects: 100% (5/5), done. remote: Total 635835 (delta 4), reused 9 (delta 4), pack-reused 635826 Receiving objects: 100% (635835/635835), 299.65 MiB | 4.39 MiB/s, done. Resolving deltas: 100% (450073/450073), done. Tapped 3994 casks (4,066 files, 320.0MB). ==\u0026gt; Downloading https://iterm2.com/downloads/stable/iTerm2-3_4_15.zip ######################################################################## 100.0% ==\u0026gt; Installing Cask iterm2 ==\u0026gt; Moving App \u0026#39;iTerm.app\u0026#39; to \u0026#39;/Applications/iTerm.app\u0026#39; 🍺 iterm2 was successfully installed! 自分はCtrl(⌃)を2回叩いてターミナルを出したり消したりできるのが好きなので、\nHotkeysを参考にitermの設定をいじっていく。\nPreferenceを開き、\u0026ldquo;Keys\u0026rdquo;→\u0026ldquo;Hotkey\u0026rdquo;→\u0026ldquo;Create a Dedicated Hotkey window\u0026hellip;\u0026ldquo;と進む。\n下記画像のように設定していく。\nまた、ターミナルを開くのはhotkeyだけにしたいので、itermの起動時に自動で新規ターミナルを開かないようにする。\n\u0026ldquo;Advanced\u0026quot;から\u0026quot;Open a new window when you click the dock icon\u0026hellip;\u0026ldquo;を\u0026quot;No\u0026quot;に変更する。\niterm2を再起動し、Ctrl(⌃)を連打するとターミナルがパカパカ開いたり閉じたりするようになる。\n他にもやりたいことはあるけど、一旦ここまで。\nおわり 新しく買ったMacBookでターミナルの設定をしてみた。\nM1チップが出た当初はbrewとかも結構大変だったみたいだけど、\nもう1年以上経っていたおかげかだいぶ設定もしやすかった。\n本当はdotfilesをちゃんと定期的に管理した方が賢いと思うけど、\n手作業で設定していくのも結構楽しい\u0026hellip;\nおまけ ","date":"2022-05-15T00:00:00Z","image":"/post/2022-05-15-setup-macbook-brew-zsh-iterm2/sotochan.jpeg","permalink":"/post/2022-05-15-setup-macbook-brew-zsh-iterm2/","title":"Setting up my MacBook Pro(2021) (brew+zsh+iterm2)"},{"content":"日本語/Japanese\nSummary sed command can convert a multi line string into a single line string without deleting \\n.\n# a multi line string $ cat text aaa bbb ccc # convert a multi line string into a single line $ cat text | sed -z \u0026#39;s/\\n//g\u0026#39; aaabbbccc # leave newline characters(\\n) $ cat text | sed -z \u0026#39;s/\\n/\\\\n/g\u0026#39; aaa\\nbbb\\nccc\\n\\n Prerequisites sed (GNU sed) 4.7 NOTE: BSD sed has different options Detail I wanted to convert a multi line string into a single line string, leaving newline characters in place.\n# a multi line string $ cat text aaa bbb ccc sed -z can treat newline characters(\\n) as normal characters.\nHere is what I often do.\n# convert a multi line string into a single line $ cat text | sed -z \u0026#39;s/\\n//g\u0026#39; aaabbbccc # \u0026#34;-z\u0026#34; cannot be omitted $ cat text | sed \u0026#39;s/\\n//g\u0026#39; aaa bbb ccc This time, I tried an another method because I had to leave newline characters.\n$ cat text | sed -z \u0026#39;s/\\n/\\\\n/g\u0026#39; aaa\\nbbb\\nccc\\n\\n Good.\nIt seems to be working.\n(The following is the same content in Japanese.)\nまとめ こんな感じで複数行の文字列を\\nを含んだまま1行にできる。\n# 複数行の文字列 $ cat text aaa bbb ccc # 普通に改行文字を消す $ cat text | sed -z \u0026#39;s/\\n//g\u0026#39; aaabbbccc # 改行文字を\\nとして表示するが改行はしない $ cat text | sed -z \u0026#39;s/\\n/\\\\n/g\u0026#39; aaa\\nbbb\\nccc\\n\\n 環境 sed (GNU sed) 4.7 Macにデフォルトで入ってるBSD版のsedだとオプションが違うので注意 詳細 複数行の文字列を改行文字(\\n)を残したまま1行に変換したいことがあった。\n# 複数行の文字列 $ cat text aaa bbb ccc sedの-zオプションを使うと改行文字(\\n)を操作できる。\nよくやるのはこんな感じ。\n# 改行文字を消して1行にする $ cat text | sed -z \u0026#39;s/\\n//g\u0026#39; aaabbbccc # -zオプションがないと改行文字を操作できない $ cat text | sed \u0026#39;s/\\n//g\u0026#39; aaa bbb ccc 今回は改行は無くしたいが改行文字(\\n)は残したかったので、\n次のように書いてみた。\n$ cat text | sed -z \u0026#39;s/\\n/\\\\n/g\u0026#39; aaa\\nbbb\\nccc\\n\\n いい感じ。\nおわり なんかJSONの値に改行文字を含むテキストの内容を入れたくなることがあったんだけど調べてもやり方がすぐに出てこなかったので試してみた。\n他にもっと簡単な方法があるかも?\nおまけ ","date":"2022-05-05T00:00:00Z","image":"/post/2022-05-05-sed-convert-multiline-into-singleline-leaving-newline-characters/sotochan.jpeg","permalink":"/post/2022-05-05-sed-convert-multiline-into-singleline-leaving-newline-characters/","title":"Convert a multi line string into a single line string leaving newline characters(LF \\n) with sed"},{"content":"まとめ つめとぎ 毛布だいすき よくしゃべる つめとぎ 先月買ったつめとぎベッドをちょっと使ってくれるようになった。\nとてもえらい。\nえらい pic.twitter.com/yqEarIA2J2\n\u0026mdash; ずみし (@uzimihsr) April 1, 2022 でもやっぱり元のつめとぎ(ソファ)が好きみたい。\nまだソファ率の方が高い pic.twitter.com/nAGGOBtgkE\n\u0026mdash; ずみし (@uzimihsr) April 1, 2022 そして現在\u0026hellip;\nついに布が破けて中身が見えるようになってしまった\u0026hellip;\n逆側の肘掛けももう長くないかもしれない😭\nあなたがバリバリしたんですよ pic.twitter.com/j7MvKmv2Oh\n\u0026mdash; ずみし (@uzimihsr) April 9, 2022 どうして\u0026hellip;\n毛布だいすき 理由は忘れちゃったけど何かの機会にベッドに敷いてる毛布とは別の毛布を出して、\n片付けるのが面倒でそのままソファに置いていたらねこに気に入られてしまった。\nこの毛布も好き pic.twitter.com/pDolLa3wOh\n\u0026mdash; ずみし (@uzimihsr) April 6, 2022 あんなに好きだったおひるねスポットの本棚の上にも登らずに昼間はずっとここで寝ている。\nねてる pic.twitter.com/JzyLHvRKqg\n\u0026mdash; ずみし (@uzimihsr) April 11, 2022 肌触りが好き?みたいで、よく体をこすりつけている。\n毛布の上に乗っているときはいつもごきげん。\nとはいえそろそろ暖かくなってきたので片付けたいが、\nなかなかお許しが出ない。\n攻撃の構え pic.twitter.com/Fi7Aoxm1p9\n\u0026mdash; ずみし (@uzimihsr) April 12, 2022 ちなみにベッドに敷いてる茶色の毛布もまだすき。\n寝てる pic.twitter.com/Pf3qVRfLuj\n\u0026mdash; ずみし (@uzimihsr) April 16, 2022 よくしゃべる そとちゃんはよくしゃべる。\n特に夕方の晩ごはん前はすごい。\nおひるねから起きてからごはんをもらうまではずっと話しかけてくる。\n割とまったりしてるときにも喋ってくれる。\nひざには乗るけどかまってほしくないときがある pic.twitter.com/WTqoIZgskF\n\u0026mdash; ずみし (@uzimihsr) April 30, 2022 これくらい喋ってくれるとコミュニケーションがとれているような気分になって楽しい。\nあとはまだ撮れたことがないけど、遊んでるときとか早朝は爆音で叫ぶのでいつか動画にしたい。\nおわり そとちゃんはいつも通り元気だった。\n何気なく動画をいくつか撮ってみたら結構楽しかったので、\nもうちょっと頻繁に撮ってもいいかもしれない。\nおまけ ","date":"2022-05-04T00:00:00Z","image":"/post/2022-05-04-sotochan/sotochan.jpeg","permalink":"/post/2022-05-04-sotochan/","title":"4月のそとちゃん(2022)"},{"content":"日本語版/Japanese\nSummary The following code is an example of reading a YAML file with the name specified by a command line flag.\n$ cat members.yaml members: - name: \u0026#34;John Doe\u0026#34; age: 20 privileged: false languages: - language: \u0026#34;Java\u0026#34; years: 4 - language: \u0026#34;Go\u0026#34; years: 2 - name: \u0026#34;Jane Doe\u0026#34; age: 30 privileged: true $ go run main.go -yaml-file members.yaml name: John Doe age: 20 privileged: false language 0: Java for 4 year(s) language 1: Go for 2 year(s) name: Jane Doe age: 30 privileged: true Prerequisites go 1.18 Detail Parse a flag with the flag package Read a YAML file with the os package Convert YAML values into a Go struct with the yaml package Parse a flag Use flag package to parse command line flags.\nIn this case, the number of flags must be 1 and no arg is allowed.\nvar yamlFile string flag.StringVar(\u0026amp;yamlFile, \u0026#34;yaml-file\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;path to the YAML file\u0026#34;) flag.Parse() if flag.NFlag() != 1 || flag.NArg() != 0 { log.Fatalf(\u0026#34;you must give one flag and no arg: #(flags)=%v, #(args)=%v\\n\u0026#34;, flag.NFlag(), flag.NArg()) } if yamlFile == \u0026#34;\u0026#34; { log.Fatalf(\u0026#34;you must specify yaml file\\n\u0026#34;) } Read a file os.Readfile can open a file to read.\n// yamlFile: string data, err := os.ReadFile(yamlFile) if err != nil { log.Fatalf(\u0026#34;ERROR in reading file: %v\\n\u0026#34;, err) } Convert YAML values into a Go struct yaml package converts YAML values into a Go struct.\nIn this case, assume the following YAML and define a struct representing values.\nmembers: - name: \u0026#34;John Doe\u0026#34; age: 20 privileged: false languages: - language: \u0026#34;Java\u0026#34; years: 4 - language: \u0026#34;Go\u0026#34; years: 2 - name: \u0026#34;Jane Doe\u0026#34; age: 30 privileged: true Some fields can be omitted if there is no need to read it.\nmap[interface{}]interface{} can be used instead of the struct, but I do not like that way.\n// data: []byte y := struct { Members []struct { Name string `yaml:\u0026#34;name\u0026#34;` Age int `yaml:\u0026#34;age\u0026#34;` Privileged bool `yaml:\u0026#34;privileged\u0026#34;` Languages []struct { Language string `yaml:\u0026#34;language\u0026#34;` Years int `yaml:\u0026#34;years\u0026#34;` } `yaml:\u0026#34;languages\u0026#34;` } `yaml:\u0026#34;members\u0026#34;` }{} err = yaml.Unmarshal(data, \u0026amp;y) if err != nil { log.Fatalf(\u0026#34;ERROR in parsing YAML: %v\\n\u0026#34;, err) } Then the values can be accessed by the field name of the struct representing the key name.\nfmt.Printf(\u0026#34;name: %v\\n\u0026#34;, y.Members[0].Name) (The following is the same content in Japanese.)\nまとめ コマンドラインで指定されたYAMLファイルを開いて要素を取り出すまでのGoコードは以下のように書けそう\n(以下コピペ用)\n$ cat members.yaml members: - name: \u0026#34;John Doe\u0026#34; age: 20 privileged: false languages: - language: \u0026#34;Java\u0026#34; years: 4 - language: \u0026#34;Go\u0026#34; years: 2 - name: \u0026#34;Jane Doe\u0026#34; age: 30 privileged: true $ go run main.go -yaml-file members.yaml name: John Doe age: 20 privileged: false language 0: Java for 4 year(s) language 1: Go for 2 year(s) name: Jane Doe age: 30 privileged: true 環境 go 1.18 詳細 flagで指定されたYAMLファイル名をコード内で使用する 指定されたファイルを開く ファイルの内容を構造体にパースしてkeyで値を取り出せる状態にする flagの処理 まずは標準のflagパッケージを使い、実行時に対象のファイル名を指定できるようにする。 4行目以降でflag数と入力値のバリデーションをかけてみたが、特に気にならなければ消してもいい。\nvar yamlFile string flag.StringVar(\u0026amp;yamlFile, \u0026#34;yaml-file\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;path to the YAML file\u0026#34;) flag.Parse() if flag.NFlag() != 1 || flag.NArg() != 0 { log.Fatalf(\u0026#34;you must give one flag and no arg: #(flags)=%v, #(args)=%v\\n\u0026#34;, flag.NFlag(), flag.NArg()) } if yamlFile == \u0026#34;\u0026#34; { log.Fatalf(\u0026#34;you must specify yaml file\\n\u0026#34;) } ファイルの読み込み 次にosパッケージで対象のファイルを開く。\n// yamlFile: string data, err := os.ReadFile(yamlFile) if err != nil { log.Fatalf(\u0026#34;ERROR in reading file: %v\\n\u0026#34;, err) } YAMLにパース 最後にyamlパッケージで対象のyamlを構造体にパースする。\n今回は下記のようなYAMLを想定し、\nその構造をパース用の構造体に定義する。\nmembers: - name: \u0026#34;John Doe\u0026#34; age: 20 privileged: false languages: - language: \u0026#34;Java\u0026#34; years: 4 - language: \u0026#34;Go\u0026#34; years: 2 - name: \u0026#34;Jane Doe\u0026#34; age: 30 privileged: true パースが不要な要素については定義しなくても問題ない。\nまた、構造体の代わりにmap[interface{}]interface{}を使う方法もあるが、\n要素を呼び出すたびに型をアサーションして使うのはあまり好きではない。\n// data: []byte y := struct { Members []struct { Name string `yaml:\u0026#34;name\u0026#34;` Age int `yaml:\u0026#34;age\u0026#34;` Privileged bool `yaml:\u0026#34;privileged\u0026#34;` Languages []struct { Language string `yaml:\u0026#34;language\u0026#34;` Years int `yaml:\u0026#34;years\u0026#34;` } `yaml:\u0026#34;languages\u0026#34;` } `yaml:\u0026#34;members\u0026#34;` }{} err = yaml.Unmarshal(data, \u0026amp;y) if err != nil { log.Fatalf(\u0026#34;ERROR in parsing YAML: %v\\n\u0026#34;, err) } 構造体にパースできたら、\nあとはフィールド名を指定して要素にアクセスができる。\nfmt.Printf(\u0026#34;name: %v\\n\u0026#34;, y.Members[0].Name) おわり 久しぶりにGoに触ったらいろいろ消耗してしまったので自分のコピペ用に書いてみた。\nYAMLを設定ファイルとして読み込むものを書く機会が多いので、出番があるといいな\u0026hellip;\nおまけ ","date":"2022-04-25T00:00:00Z","image":"/post/2022-04-25-read-yaml-file-golang/sotochan.jpeg","permalink":"/post/2022-04-25-read-yaml-file-golang/","title":"Read the specified YAML file with golang"},{"content":"日本語版/Japanese\nSummary yq is very useful for reading or updating Kubernetes YAML.\n## read the value(key=spec.containers[0].image) from YAML(pod.yaml) # specify the file yq \u0026#39;.spec.containers[0].image\u0026#39; pod.yaml # pipe from stdout cat pod.yaml | yq \u0026#39;.spec.containers[0].image\u0026#39; ## update the YAML value(key=spec.containers[0].image) to \u0026#34;nginx:1.20.2\u0026#34; # stdout cat pod.yaml | yq \u0026#39;.spec.containers[0].image = \u0026#34;nginx:1.20.2\u0026#34;\u0026#39; # update the file yq -i \u0026#39;.spec.containers[0].image = \u0026#34;nginx:1.20.2\u0026#34;\u0026#39; pod.yaml Prerequisites yq version 4.24.2 Read and Update Kubernetes YAML with yq Let\u0026rsquo;s read and update the pod.yaml with yq.\nRead We can extract the value by specifying the key.\n# extract the image name $ yq \u0026#39;.spec.containers[0].image\u0026#39; pod.yaml nginx:latest # extract the tag $ yq \u0026#39;.spec.containers[0].image\u0026#39; pod.yaml | grep -o \u0026#39;^[^:]\\+\u0026#39; nginx $ yq \u0026#39;.spec.containers[0].image\u0026#39; pod.yaml | grep -o \u0026#39;[^:]\\+$\u0026#39; latest # pipe from stdout $ cat pod.yaml | yq \u0026#39;.spec.containers[0].image\u0026#39; nginx:latest $ cat pod.yaml | yq \u0026#39;.spec.containers[0].image\u0026#39; | grep -o \u0026#39;^[^:]\\+\u0026#39; nginx $ cat pod.yaml | yq \u0026#39;.spec.containers[0].image\u0026#39; | grep -o \u0026#39;[^:]\\+$\u0026#39; latest Update You can change the value of the specified key.\n# change the image name $ cat pod.yaml | yq \u0026#39;.spec.containers[0].image = \u0026#34;nginx:1.20.2\u0026#34;\u0026#39; apiVersion: v1 kind: Pod metadata: name: nginx-pod spec: containers: - name: nginx image: nginx:1.20.2 # update the file directly $ yq -i \u0026#39;.spec.containers[0].image = \u0026#34;nginx:1.20.2\u0026#34;\u0026#39; pod.yaml $ cat pod.yaml apiVersion: v1 kind: Pod metadata: name: nginx-pod spec: containers: - name: nginx image: nginx:1.20.2 (The following is the same content in Japanese.)\nまとめ yqはv4でもやっぱり便利。\n## YAML(pod.yaml)の値(key=spec.containers[0].image)を抽出 # ファイル名を指定するパターン yq \u0026#39;.spec.containers[0].image\u0026#39; pod.yaml # 標準出力からパイプで受けるパターン cat pod.yaml | yq \u0026#39;.spec.containers[0].image\u0026#39; ## YAML(pod.yaml)の値(key=spec.containers[0].image)を\u0026#34;nginx:1.20.2\u0026#34;で上書き # 標準出力に出すだけ cat pod.yaml | yq \u0026#39;.spec.containers[0].image = \u0026#34;nginx:1.20.2\u0026#34;\u0026#39; # ファイルの値を直接更新する yq -i \u0026#39;.spec.containers[0].image = \u0026#34;nginx:1.20.2\u0026#34;\u0026#39; pod.yaml 環境 yq version 4.24.2 v3については過去記事を参照 yqでKubernetes YAMLをいじる こんな感じの pod.yaml をyqでいじる。\n読む keyを指定して特定の要素を取り出す。\n# image名を抜き出す $ yq \u0026#39;.spec.containers[0].image\u0026#39; pod.yaml nginx:latest # grepと合わせてtagを取り出す $ yq \u0026#39;.spec.containers[0].image\u0026#39; pod.yaml | grep -o \u0026#39;^[^:]\\+\u0026#39; nginx $ yq \u0026#39;.spec.containers[0].image\u0026#39; pod.yaml | grep -o \u0026#39;[^:]\\+$\u0026#39; latest # パイプで渡すパターン $ cat pod.yaml | yq \u0026#39;.spec.containers[0].image\u0026#39; nginx:latest $ cat pod.yaml | yq \u0026#39;.spec.containers[0].image\u0026#39; | grep -o \u0026#39;^[^:]\\+\u0026#39; nginx $ cat pod.yaml | yq \u0026#39;.spec.containers[0].image\u0026#39; | grep -o \u0026#39;[^:]\\+$\u0026#39; latest 更新する 指定したkeyの値を上書きする。\n# image名を更新する $ cat pod.yaml | yq \u0026#39;.spec.containers[0].image = \u0026#34;nginx:1.20.2\u0026#34;\u0026#39; apiVersion: v1 kind: Pod metadata: name: nginx-pod spec: containers: - name: nginx image: nginx:1.20.2 # ファイルを直接上書きする $ yq -i \u0026#39;.spec.containers[0].image = \u0026#34;nginx:1.20.2\u0026#34;\u0026#39; pod.yaml $ cat pod.yaml apiVersion: v1 kind: Pod metadata: name: nginx-pod spec: containers: - name: nginx image: nginx:1.20.2 おわり yqのv3→v4へのアップデートでかなり大規模な変更があり、\n過去に書いた記事の内容がそのままだと使えなくなってしまっていたので急いで書いてみた。\n今までずっとv3を使っていたので、v4になった途端動かなくてびっくりした\u0026hellip;\nおまけ ","date":"2022-04-14T00:00:00Z","image":"/post/2022-04-14-yq-v4-kubernetes-yaml/sotochan.jpeg","permalink":"/post/2022-04-14-yq-v4-kubernetes-yaml/","title":"Read and update Kubernetes YAML with yq(v4)"},{"content":"まとめ ひっくり返る 病院 つめとぎベッド ひっくり返る そとちゃんはいつもごろごろしている。\nごろごろ pic.twitter.com/QqaIjjgXzv\n\u0026mdash; ずみし (@uzimihsr) March 10, 2022 床でもベッドでも俺の上でも、機嫌がいいときは常にごろごろ。\nごきげん pic.twitter.com/SVYYmPLxZE\n\u0026mdash; ずみし (@uzimihsr) March 31, 2022 鼻と口の⅄がひっくり返ってるところがたまらなくかわいい。\nY pic.twitter.com/4tXzJK0Acc\n\u0026mdash; ずみし (@uzimihsr) March 16, 2022 \u0026hellip;たまにひっくり返りすぎてちょっと怖い顔になってるのもおもしろい。\n？ pic.twitter.com/NxjWvGcW1w\n\u0026mdash; ずみし (@uzimihsr) March 6, 2022 病院 12月に受けた手術のときに血液検査の数値が少し怪しかったそとちゃん。\n予定通り3ヶ月経ったので再検査にいった。\n以前はお出かけの気配を察して自分から進んでキャリーに入ってたのに、\n手術がよっぽど嫌だったのか今回はおもちゃで釣らないと入ってくれなかった。\n病院いく pic.twitter.com/nrApEsqhtX\n\u0026mdash; ずみし (@uzimihsr) March 25, 2022 病院の待合室でたまたま向かいに別のねこちゃんが居て、\nキャリー越しにいっぱい話しかけてくれた(ような気がした)んだけどそとちゃんはガン無視。\n他のねこには興味がないのかもしれない。\n(普段ねこが出てるテレビ番組とか動画を見せてもそんなに食いつかない)\n今回は前回までと違うベテランの先生で、\nいつもは診察室でやりたい放題のそとちゃんがあっさり捕まって検査に連れて行かれたのがちょっとおもしろかった。\n血液検査の結果は良好。\n12月は1.7mg/dLだったクレアチニンの値が今回は1.2mg/dLまで下がっていた。\nクレアチニンの値には元々波があって、前回のようにたまに高い値が出ることがあるらしい。\n本当に腎臓が悪くなっていると慢性的に高い値で推移するようになるので、\n今回低い値がでたのは良い傾向ということだった。\nその他聴診や触診も問題なく、追加の通院や検査も不要になって安心した。\n割と健康児なので年1回の健康診断で十分らしい。🎉\nちなみに、血液検査の結果で頭がいっぱいで最近のごはんの量について相談するのを忘れてしまった\u0026hellip;\n体重が3.98kgと少し軽めだったので、おやつの量をちょっと増やすくらいはいいのかも？\n病院がんばった pic.twitter.com/sr6dVRJG70\n\u0026mdash; ずみし (@uzimihsr) March 26, 2022 つめとぎベッド そとちゃんのつめとぎ(俺のソファ)がボロボロで流石に壊れそうなので、\n新しいつめとぎベッドを買ってみた。\n新しい爪とぎを献上 pic.twitter.com/1tkteN4kDq\n\u0026mdash; ずみし (@uzimihsr) March 26, 2022 が、やはり使い慣れたつめとぎが良いらしく\u0026hellip;\nバリバリ pic.twitter.com/b7meXIxq0F\n\u0026mdash; ずみし (@uzimihsr) March 27, 2022 結局ソファは破壊されるもよう。\n新しく買ったつめとぎガン無視で笑っちゃう pic.twitter.com/mqYvrSjGSB\n\u0026mdash; ずみし (@uzimihsr) March 29, 2022 ベッドということは理解しているみたいで、\n一応乗ったり寝転がったりはするんだけどそこからなかなかつめとぎに発展しない。\nせっかく買ったのに\u0026hellip;😭\n乗るけど爪とぎはしない pic.twitter.com/r05aU3njoR\n\u0026mdash; ずみし (@uzimihsr) March 28, 2022 おわり 3月もそとちゃんは元気だった。\n検査の結果も問題なしで、本当に手がかからなくて助かる。\nこんなに一緒に暮らしやすい子は他に居ないんじゃないか??(親バカ)\nおまけ ","date":"2022-04-07T00:00:00Z","image":"/post/2022-04-07-sotochan/sotochan.jpeg","permalink":"/post/2022-04-07-sotochan/","title":"3月のそとちゃん(2022)"},{"content":"まとめ めっちゃ食べる 毛布 めっちゃ食べる そとちゃんの食欲がすごい。\n今までも晩ごはんのウェットフード(猫缶パウチ)は残さずきれいに食べるえらいねこだったんだけど、\n最近はカリカリもきれいに平らげるようになった。\n俺が居てもカリカリ食べるようになった\nえらい pic.twitter.com/yKkktcpss7\n\u0026mdash; ずみし (@uzimihsr) February 17, 2022 そとちゃんの一日の食事はだいたいこんな感じ。\n時刻 ごはん エネルギー(kcal) 8:00 AM 療法食カリカリ約10g\n(カリカリマシーンで出る) 40 13:00 PM 療法食カリカリ約10g\n(カリカリマシーン) 40 18:00 PM 黒缶パウチ+かつお節トッピング 56+15 不定期(主に就寝前) おやつのにぼし+かにかま 5+7 合計 163 体重約4.0kgのそとちゃんの場合1日の摂取カロリーは132~190kcalが適切らしいが、\nそとちゃんはちょっと少食で、今まではこの量でもお昼のカリカリを残すことがあった。\n(味にうるさいのもあるが\u0026hellip;)\nしかし手術後に元気が倍増したそとちゃんはごはんをもりもり食べる。\n以前よりも運動量が増えたせいでお腹が空いているのか、カリカリもほぼ毎回完食。\nカリカリマシーンの音でそとちゃんが駆け寄ってくるなんて以前は考えられなかった光景で、初めて見たときはかなり驚いた。\n少し味覚が大人になったのかもしれないが、理由は何であれごはんをよく食べるようになってとてもえらい。\n最近はごはんをおねだりすることも増えたので(めっちゃもじもじしてかわいい)、\nあげすぎないように注意したい。\n正解 pic.twitter.com/7ctanyd0Yb\n\u0026mdash; ずみし (@uzimihsr) February 19, 2022 毛布 俺のベッドに敷いてる毛布が好き。\n毛布食べないで欲しい pic.twitter.com/2ZDquN2JYy\n\u0026mdash; ずみし (@uzimihsr) February 1, 2022 毎朝暴れて疲れたあとはだいたい毛布の上でごろごろしている。\nしっぽ自分で踏んでる pic.twitter.com/Ek1z3OwLcM\n\u0026mdash; ずみし (@uzimihsr) February 7, 2022 お昼寝のあとから夕方もたまにごろごろしている。\nぐにゃぐにゃしてる pic.twitter.com/wJsOFQHVRY\n\u0026mdash; ずみし (@uzimihsr) February 9, 2022 だいぶ昔にド○キで適当に買った安物なんだけどなにがそんなに良いのだろうか\u0026hellip;\nスフィンクス pic.twitter.com/4PJWM0HOlt\n\u0026mdash; ずみし (@uzimihsr) February 24, 2022 ごろごろと遊びは毛布の上でするんだけど、そこで寝ることはめったに無い。\nそとちゃんの中で寝る場所はキャットタワーと本棚の上だけと決まっているように見える。\nむずかしい。\nうおー pic.twitter.com/0fiae2qbGd\n\u0026mdash; ずみし (@uzimihsr) February 16, 2022 ちなみに毛布だけじゃなくて布団も上に乗るのだけは好き。\n俺が寝てると上に乗ってくる。\n起きられません pic.twitter.com/90WkQLkVj3\n\u0026mdash; ずみし (@uzimihsr) February 6, 2022 が、ガードが固い？ので布団の中に入ってくることは絶対にない。\nたまに同衾チャレンジするんだけどすぐに抜け出してマウントポジションを取られる。\nとにかく布を上からかけられるのが嫌みたい。\nむずかしい\u0026hellip;\nおわり そとちゃんが変わらず元気いっぱいで嬉しい。\n運動量が明らかに増えたのでごはんの量はちょっと増やしてもいいのかな\u0026hellip;?\n3月に定期検診で病院に行くので、先生とも相談したい。\nおまけ ","date":"2022-03-10T00:00:00Z","image":"/post/2022-03-10-sotochan/sotochan.jpeg","permalink":"/post/2022-03-10-sotochan/","title":"2月のそとちゃん(2022)"},{"content":"Summary This is my note on running CentOS Stream 8 via VirtualBox on MacOS.\nPrerequisites macOS Big Sur 11.6.4 2.5GHz quad-core Intel Core i7 VirtualBox 6.1.32 CentOS Stream release 8 Linux version 4.18.0-365.el8.x86_64 What I did Install VirtualBox Create a VM Install CentOS Stream 8 Install Guest Additions Install VirtualBox Install the dmg for OS X hosts from https://www.virtualbox.org/ then run VirtualBox.pkg.\nFollow the instructions and VirtualBox will appear on /Applications.\nCreate a VM Start VirtualBox and click \u0026ldquo;New\u0026rdquo; to create a VM.\nMy settings are as follows.\nThe following is an overview of the VM I created.\nInstall CentOS Stream 8 In order to run CentOS on a VM, you have to download the image from https://www.centos.org/download/ .\nI selected CentOS Stream 8(x86_64) for this time.\nAny mirror will do, but you need to download CentOS-Stream-8-x86_64-YYYYMMDD-dvd1.iso.\nThe file is about 10GB and took about 20 minutes or more to download.\nAfter downloading the .iso file, mount it in the VM on VirtualBox.\nIn the \u0026ldquo;Storage\u0026rdquo; section, specify the downloaded .iso file for \u0026ldquo;IDE Secondary Device 0\u0026rdquo;.\nClick \u0026ldquo;Start\u0026rdquo;.\nA new window will appears. Select \u0026ldquo;Install CentOS Stream 8-stream\u0026rdquo;.\nSelect any language you use in the next window.\nWhen the following window appears, fix the options marked with a color.\nI configured \u0026ldquo;Installation Destination\u0026rdquo;, \u0026ldquo;Root Password\u0026rdquo;, and \u0026ldquo;User Creation\u0026rdquo; for this time.\nAfter configuration, click \u0026ldquo;Begin Installation\u0026rdquo;.\nIt took about 10 minutes to install.\nAfter installation, click \u0026ldquo;Reboot System\u0026rdquo;.\nA new window will appear.\nAccept the license and click \u0026ldquo;FINISH CONFIGURATION\u0026rdquo;.\nSome option windows will appear, but you can proceed as you wish.\nFinally you can see GNOME Desktop.🎉\nAt first, update rpm packages.\nLaunch a terminal from \u0026ldquo;Activities\u0026rdquo; in the upper-left corner then run yum update.\nOops! I met the error \u0026ldquo;Could not resolve host\u0026rdquo;.\n[uzimihsr@localhost ~]$ sudo yum update -y CentOS Stream 8 - AppStream 0.0 B/s | 0 B 00:00 Errors during downloading metadata for repository \u0026#39;appstream\u0026#39;: - Curl error (6): Couldn\u0026#39;t resolve host name for http://mirrorlist.centos.org/?release=8-stream\u0026amp;arch=x86_64\u0026amp;repo=AppStream\u0026amp;infra=stock [Could not resolve host: mirrorlist.centos.org] Error: Failed to download metadata for repo \u0026#39;appstream\u0026#39;: Cannot prepare internal mirrorlist: Curl error (6): Couldn\u0026#39;t resolve host name for http://mirrorlist.centos.org/?release=8-stream\u0026amp;arch=x86_64\u0026amp;repo=AppStream\u0026amp;infra=stock [Could not resolve host: mirrorlist.centos.org] You can see the status in the upper-right corner shows \u0026ldquo;Wired Off\u0026rdquo;.\nGo to \u0026ldquo;Wired Settings\u0026rdquo; \u0026gt; \u0026ldquo;⚙️\u0026rdquo; and enable \u0026ldquo;Connect automatically\u0026rdquo;.\nNow we can see \u0026ldquo;Wired Connected\u0026rdquo; in the status.\nTry yum update again. It will succeed this time.\nsudo yum update -y Install Guest Additions Guest Additions make it easy to integrate VirtualBox VMs and the host OS.\nhttps://www.virtualbox.org/manual/ch04.html\nBack to the host OS.\nSelect \u0026ldquo;VirtualBox VM\u0026rdquo;→\u0026ldquo;Devices\u0026rdquo;→\u0026ldquo;Insert Guest Additions CD image\u0026hellip;\u0026rdquo;.\nYou will see some windows with warnings about the software or a request for the administrator password, but proceed anyway.\nA new terminal will appear and the scripts will run on it.\nAww. I met the error \u0026ldquo;Kernel headers not found for target kernel\u0026rdquo;.\nVerifying archive integrity... All good. Uncompressing VirtualBox 6.1.32 Guest Additions for Linux........ VirtualBox Guest Additions installer Copying additional installer modules ... Installing additional modules ... VirtualBox Guest Additions: Starting. VirtualBox Guest Additions: Building the VirtualBox Guest Additions kernel modules. This may take a while. VirtualBox Guest Additions: To build modules for other installed kernels, run VirtualBox Guest Additions: /sbin/rcvboxadd quicksetup \u0026lt;version\u0026gt; VirtualBox Guest Additions: or VirtualBox Guest Additions: /sbin/rcvboxadd quicksetup all VirtualBox Guest Additions: Kernel headers not found for target kernel 4.18.0-365.el8.x86_64. Please install them and execute /sbin/rcvboxadd setup modprobe vboxguest failed The log file /var/log/vboxadd-setup.log may contain further information. Press Return to close this window... Let\u0026rsquo;s install kernel-headers and kernel-devel, then reboot the OS.\nsudo yum install -y kernel-headers kernel-devel sudo reboot After rebooting, open a terminal and execute the command shown in the previous message.\nOh, no. I met the different error \u0026ldquo;Please install the gcc make perl packages from your distribution\u0026rdquo;.\n[uzimihsr@localhost ~]$ sudo /sbin/rcvboxadd setup VirtualBox Guest Additions: Starting. VirtualBox Guest Additions: Building the VirtualBox Guest Additions kernel modules. This may take a while. VirtualBox Guest Additions: To build modules for other installed kernels, run VirtualBox Guest Additions: /sbin/rcvboxadd quicksetup \u0026lt;version\u0026gt; VirtualBox Guest Additions: or VirtualBox Guest Additions: /sbin/rcvboxadd quicksetup all VirtualBox Guest Additions: Building the modules for kernel 4.18.0-365.el8.x86_64. This system is currently not set up to build kernel modules. Please install the gcc make perl packages from your distribution. ValueError: File context for /opt/VBoxGuestAdditions-6.1.32/other/mount.vboxsf already defined modprobe vboxguest failed The log file /var/log/vboxadd-setup.log may contain further information. Install them.\nsudo yum install -y gcc make perl Execute the command again.\nThe new error came out, but I couldn\u0026rsquo;t figure it out.\n[uzimihsr@localhost ~]$ sudo /sbin/rcvboxadd setup VirtualBox Guest Additions: Starting. VirtualBox Guest Additions: Building the VirtualBox Guest Additions kernel modules. This may take a while. VirtualBox Guest Additions: To build modules for other installed kernels, run VirtualBox Guest Additions: /sbin/rcvboxadd quicksetup \u0026lt;version\u0026gt; VirtualBox Guest Additions: or VirtualBox Guest Additions: /sbin/rcvboxadd quicksetup all VirtualBox Guest Additions: Building the modules for kernel 4.18.0-365.el8.x86_64. VirtualBox Guest Additions: Look at /var/log/vboxadd-setup.log to find out what went wrong ValueError: File context for /opt/VBoxGuestAdditions-6.1.32/other/mount.vboxsf already defined modprobe vboxguest failed The log file /var/log/vboxadd-setup.log may contain further information. Let\u0026rsquo;s open the log file to see the details.\nIt said \u0026ldquo;please install libelf-dev, libelf-devel or elfutils-libelf-devel\u0026rdquo;.\n[uzimihsr@localhost ~]$ cat /var/log/vboxadd-setup.log Building the main Guest Additions 6.1.32 module for kernel 4.18.0-365.el8.x86_64. Error building the module. Build output follows. make V=1 CONFIG_MODULE_SIG= CONFIG_MODULE_SIG_ALL= -C /lib/modules/4.18.0-365.el8.x86_64/build M=/tmp/vbox.0 SRCROOT=/tmp/vbox.0 -j1 modules Makefile:985: *** \u0026#34;Cannot generate ORC metadata for CONFIG_UNWINDER_ORC=y, please install libelf-dev, libelf-devel or elfutils-libelf-devel\u0026#34;. Stop. make: *** [/tmp/vbox.0/Makefile-footer.gmk:117: vboxguest] Error 2 modprobe vboxguest failed I tried yum search for the package, but there was only elfutils-libelf-devel.\nInstall it.\nsudo yum install -y elfutils-libelf-devel Try again.\nMaybe it worked?\n[uzimihsr@localhost ~]$ sudo /sbin/rcvboxadd setup VirtualBox Guest Additions: Starting. VirtualBox Guest Additions: Building the VirtualBox Guest Additions kernel modules. This may take a while. VirtualBox Guest Additions: To build modules for other installed kernels, run VirtualBox Guest Additions: /sbin/rcvboxadd quicksetup \u0026lt;version\u0026gt; VirtualBox Guest Additions: or VirtualBox Guest Additions: /sbin/rcvboxadd quicksetup all VirtualBox Guest Additions: Building the modules for kernel 4.18.0-365.el8.x86_64. VirtualBox Guest Additions: Look at /var/log/vboxadd-setup.log to find out what went wrong ValueError: File context for /opt/VBoxGuestAdditions-6.1.32/other/mount.vboxsf already defined VirtualBox Guest Additions: Running kernel modules will not be replaced until the system is restarted [uzimihsr@localhost ~]$ echo $? 0 Although there is another compilation error in the log file, but the exit code of the command was 0, and the message told me to reboot to let kernels load the modules.\n[uzimihsr@localhost ~]$ cat /var/log/vboxadd-setup.log Building the main Guest Additions 6.1.32 module for kernel 4.18.0-365.el8.x86_64. Building the shared folder support module. Building the graphics driver module. Error building the module. Build output follows. make V=1 CONFIG_MODULE_SIG= CONFIG_MODULE_SIG_ALL= -C /lib/modules/4.18.0-365.el8.x86_64/build M=/tmp/vbox.0 SRCROOT=/tmp/vbox.0 -j1 modules test -e include/generated/autoconf.h -a -e include/config/auto.conf || (\t\\ echo \u0026gt;\u0026amp;2;\t\\ echo \u0026gt;\u0026amp;2 \u0026#34; ERROR: Kernel configuration is invalid.\u0026#34;;\t\\ echo \u0026gt;\u0026amp;2 \u0026#34; include/generated/autoconf.h or include/config/auto.conf are missing.\u0026#34;;\\ echo \u0026gt;\u0026amp;2 \u0026#34; Run \u0026#39;make oldconfig \u0026amp;\u0026amp; make prepare\u0026#39; on kernel src to fix it.\u0026#34;;\t\\ echo \u0026gt;\u0026amp;2 ;\t\\ /bin/false) mkdir -p /tmp/vbox.0/.tmp_versions ; rm -f /tmp/vbox.0/.tmp_versions/* make -f ./scripts/Makefile.build obj=/tmp/vbox.0 (cat /dev/null; echo kernel//tmp/vbox.0/vboxvideo.ko;) \u0026gt; /tmp/vbox.0/modules.order gcc -Wp,-MD,/tmp/vbox.0/.hgsmi_base.o.d -nostdinc -isystem /usr/lib/gcc/x86_64-redhat-linux/8/include -I./arch/x86/include -I./arch/x86/include/generated -I./include/drm-backport -I./include -I./arch/x86/include/uapi -I./arch/x86/include/generated/uapi -I./include/uapi -I./include/generated/uapi -include ./include/linux/kconfig.h -include ./include/linux/compiler_types.h -D__KERNEL__ -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -fshort-wchar -Werror-implicit-function-declaration -Wno-format-security -std=gnu89 -fno-PIE -DCC_HAVE_ASM_GOTO -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -mno-avx -m64 -falign-jumps=1 -falign-loops=1 -mno-80387 -mno-fp-ret-in-387 -mpreferred-stack-boundary=3 -mskip-rax-setup -mtune=generic -mno-red-zone -mcmodel=kernel -funit-at-a-time -DCONFIG_AS_CFI=1 -DCONFIG_AS_CFI_SIGNAL_FRAME=1 -DCONFIG_AS_CFI_SECTIONS=1 -DCONFIG_AS_SSSE3=1 -DCONFIG_AS_CRC32=1 -DCONFIG_AS_AVX=1 -DCONFIG_AS_AVX2=1 -DCONFIG_AS_AVX512=1 -DCONFIG_AS_SHA1_NI=1 -DCONFIG_AS_SHA256_NI=1 -DCONFIG_TPAUSE=1 -pipe -Wno-sign-compare -fno-asynchronous-unwind-tables -mindirect-branch=thunk-extern -mindirect-branch-register -fno-jump-tables -fno-delete-null-pointer-checks -Wno-frame-address -Wno-format-truncation -Wno-format-overflow -Wno-int-in-bool-context -O2 --param=allow-store-data-races=0 -Wframe-larger-than=2048 -fstack-protector-strong -Wno-unused-but-set-variable -Wno-unused-const-variable -g -gdwarf-4 -pg -mrecord-mcount -mfentry -DCC_USING_FENTRY -fno-inline-functions-called-once -Wdeclaration-after-statement -Wno-pointer-sign -Wno-stringop-truncation -Wno-maybe-uninitialized -fno-strict-overflow -fno-merge-all-constants -fmerge-constants -fno-stack-check -fconserve-stack -Werror=implicit-int -Werror=strict-prototypes -Werror=date-time -Werror=incompatible-pointer-types -Werror=designated-init -fmacro-prefix-map=./= -Wno-packed-not-aligned -I./include -I/tmp/vbox.0/ -I./include/drm -D__KERNEL__ -DMODULE -DRT_WITHOUT_PRAGMA_ONCE -DRT_ARCH_AMD64 -DMODULE -DKBUILD_BASENAME=\u0026#39;\u0026#34;hgsmi_base\u0026#34;\u0026#39; -DKBUILD_MODNAME=\u0026#39;\u0026#34;vboxvideo\u0026#34;\u0026#39; -c -o /tmp/vbox.0/.tmp_hgsmi_base.o /tmp/vbox.0/hgsmi_base.c In file included from /tmp/vbox.0/hgsmi_base.c:27: /tmp/vbox.0/vbox_drv.h:175:11: fatal error: drm/ttm/ttm_memory.h: No such file or directory # include \u0026lt;drm/ttm/ttm_memory.h\u0026gt; ^~~~~~~~~~~~~~~~~~~~~~ compilation terminated. make[2]: *** [scripts/Makefile.build:316: /tmp/vbox.0/hgsmi_base.o] Error 1 make[1]: *** [Makefile:1577: _module_/tmp/vbox.0] Error 2 make: *** [/tmp/vbox.0/Makefile-footer.gmk:117: vboxvideo] Error 2 [uzimihsr@localhost ~]$ sudo reboot Now the mouse pointer moves seamlessly. Yeah.\nAs a final touch, enable the shared clipboard.\nOn VirtualBox, set \u0026ldquo;Settings\u0026rdquo; \u0026gt; \u0026ldquo;General\u0026rdquo; \u0026gt; \u0026ldquo;Advanced\u0026rdquo; \u0026gt; \u0026ldquo;Shared Clipboard\u0026rdquo; to \u0026ldquo;Bidirectional\u0026rdquo;. Now you can copy and paste across the VM(CentOS) and the host(macOS).\nThat\u0026rsquo;s it for this time.\n(The following is the same content in Japanese.)\nまとめ 久しぶりにVirtualBoxで遊ぼうとしたら動かなくなってたので最初から環境構築しなおした。\nいろいろ詰まったので手順を残しておく。\n環境 macOS Big Sur 11.6.4 2.5GHz quad-core Intel Core i7 VirtualBox 6.1.32 CentOS Stream release 8 Linux version 4.18.0-365.el8.x86_64 やったこと VirtualBoxのインストール VMの作成 CentOS Stream 8のインストールとセットアップ Guest Additionsの導入 VirtualBoxのインストール https://www.virtualbox.org/ からOSX用のdmgファイルをダウンロードして中に入っているインストーラを起動する。\nインストーラを起動して指示に従っていけば/Applications配下にVirtualBoxがインストールされる。\nVMの作成 VirtualBoxを起動して\u0026quot;New\u0026quot;からVMの作成を行う。\n名前は適当で、メモリのサイズも無理のない範囲で選ぶ。\nストレージは32GB程度確保する。\n作成したVMの情報はこんな感じ。\nCentOS Stream 8のインストールとセットアップ 作成したVMで起動するOSのイメージが必要なので、\nhttps://www.centos.org/download/ から持ってくる。\n今回はx86_64用CentOS Stream 8を選択。\nミラーはどこでもいいのでCentOS-Stream-8-x86_64-YYYYMMDD-dvd1.isoをダウンロードする。\n10GBくらいあってダウンロードには30分程度かかった。\nisoを落とせたら再度VirtualBoxに戻り、\n先程作成したVMの設定の\u0026quot;Storage\u0026quot;で\u0026quot;IDE Secondary Device 0\u0026quot;を押して先程ダウンロードしたisoファイルを選択した状態で\u0026quot;Start\u0026quot;する。\n別ウインドウが出るので\u0026quot;Install CentOS Stream 8-stream\u0026quot;を押す。\nCentOSのインストール画面が開くので適当に言語を選んで進む。\n次の画面になったら色が変わっている項目を埋める。\n今回は\u0026quot;Installation Destination\u0026quot;, \u0026ldquo;Root Password\u0026rdquo;, \u0026ldquo;User Creation\u0026quot;だけいじった。\n\u0026ldquo;Begin Installation\u0026quot;を押してインストールを開始。\nインストールは10分ちょっとかかる。\nインストールが終わったら\u0026quot;Reboot System\u0026quot;から再起動する。\n再起動が成功するとライセンスに同意しろ的な画面が出るので同意した上で\u0026quot;FINISH CONFIGURATION\u0026quot;を押す。\nまたまた言語とかの設定画面が出るが適当に選択すればデスクトップ画面にたどり着ける。\nまずはパッケージを更新したいので、左上の\u0026quot;Activities\u0026quot;から端末を開いてyum updateする。\nすると名前解決できないぞ的なエラーに。\n[uzimihsr@localhost ~]$ sudo yum update -y CentOS Stream 8 - AppStream 0.0 B/s | 0 B 00:00 Errors during downloading metadata for repository \u0026#39;appstream\u0026#39;: - Curl error (6): Couldn\u0026#39;t resolve host name for http://mirrorlist.centos.org/?release=8-stream\u0026amp;arch=x86_64\u0026amp;repo=AppStream\u0026amp;infra=stock [Could not resolve host: mirrorlist.centos.org] Error: Failed to download metadata for repo \u0026#39;appstream\u0026#39;: Cannot prepare internal mirrorlist: Curl error (6): Couldn\u0026#39;t resolve host name for http://mirrorlist.centos.org/?release=8-stream\u0026amp;arch=x86_64\u0026amp;repo=AppStream\u0026amp;infra=stock [Could not resolve host: mirrorlist.centos.org] いろいろ見てると右上のステータスで\u0026quot;Wired Off\u0026quot;になっていて、どうやらネットワークにつながっていないっぽい。\n\u0026ldquo;Wired Settings\u0026rdquo;→\u0026quot;⚙️\u0026quot;を開いて\u0026quot;Connect automatically\u0026quot;にチェックを入れて適用する。\nすると右上に\u0026quot;wired connected\u0026quot;のマークが出るようになるので再度パッケージ更新を試す。\n今度はうまく行った。\nsudo yum update -y Guest Additionsの導入 VirtualBoxで立てたVMにGuest Additionsっていうのを入れるとホストOSとシームレスにGUI(マウス)操作ができたりして色々便利らしい。\nhttps://www.virtualbox.org/manual/ch04.html\nホスト(Mac)側の操作に戻り、\n\u0026ldquo;VirtualBox VM\u0026rdquo;→\u0026ldquo;Devices\u0026rdquo;→\u0026ldquo;Insert Guest Additions CD image\u0026hellip;\u0026ldquo;を選択。\nなんかVMの画面でソフトウェアの警告が出たり管理者権限を要求されるけどガンガン進む。\n勝手に新しい端末が開いてスクリプトが動く。\n\u0026hellip;が、\u0026ldquo;モジュールのビルドに必要なカーネルヘッダがないよ\u0026quot;的なエラーで失敗する。\nVerifying archive integrity... All good. Uncompressing VirtualBox 6.1.32 Guest Additions for Linux........ VirtualBox Guest Additions installer Copying additional installer modules ... Installing additional modules ... VirtualBox Guest Additions: Starting. VirtualBox Guest Additions: Building the VirtualBox Guest Additions kernel modules. This may take a while. VirtualBox Guest Additions: To build modules for other installed kernels, run VirtualBox Guest Additions: /sbin/rcvboxadd quicksetup \u0026lt;version\u0026gt; VirtualBox Guest Additions: or VirtualBox Guest Additions: /sbin/rcvboxadd quicksetup all VirtualBox Guest Additions: Kernel headers not found for target kernel 4.18.0-365.el8.x86_64. Please install them and execute /sbin/rcvboxadd setup modprobe vboxguest failed The log file /var/log/vboxadd-setup.log may contain further information. Press Return to close this window... 言われたとおりkernel-headersとkernel-develを入れる。\nカーネル関連のパッケージを入れたときは再起動。\nsudo yum install -y kernel-headers kernel-devel sudo reboot 再起動後、さっきのエラーメッセージでカーネルヘッダのインストール後に実行するよう指定されていたコマンドを打つ。\nまたエラー。つらい。\n今度は\u0026rdquo;gcc, make, perlが無いよ\u0026quot;と言われた。\n[uzimihsr@localhost ~]$ sudo /sbin/rcvboxadd setup VirtualBox Guest Additions: Starting. VirtualBox Guest Additions: Building the VirtualBox Guest Additions kernel modules. This may take a while. VirtualBox Guest Additions: To build modules for other installed kernels, run VirtualBox Guest Additions: /sbin/rcvboxadd quicksetup \u0026lt;version\u0026gt; VirtualBox Guest Additions: or VirtualBox Guest Additions: /sbin/rcvboxadd quicksetup all VirtualBox Guest Additions: Building the modules for kernel 4.18.0-365.el8.x86_64. This system is currently not set up to build kernel modules. Please install the gcc make perl packages from your distribution. ValueError: File context for /opt/VBoxGuestAdditions-6.1.32/other/mount.vboxsf already defined modprobe vboxguest failed The log file /var/log/vboxadd-setup.log may contain further information. 言われたとおりに入れる。\nsudo yum install -y gcc make perl 再度実行。\nまたまたエラー。\n[uzimihsr@localhost ~]$ sudo /sbin/rcvboxadd setup VirtualBox Guest Additions: Starting. VirtualBox Guest Additions: Building the VirtualBox Guest Additions kernel modules. This may take a while. VirtualBox Guest Additions: To build modules for other installed kernels, run VirtualBox Guest Additions: /sbin/rcvboxadd quicksetup \u0026lt;version\u0026gt; VirtualBox Guest Additions: or VirtualBox Guest Additions: /sbin/rcvboxadd quicksetup all VirtualBox Guest Additions: Building the modules for kernel 4.18.0-365.el8.x86_64. VirtualBox Guest Additions: Look at /var/log/vboxadd-setup.log to find out what went wrong ValueError: File context for /opt/VBoxGuestAdditions-6.1.32/other/mount.vboxsf already defined modprobe vboxguest failed The log file /var/log/vboxadd-setup.log may contain further information. 今度は何もわからないので、指定されたログファイルを確認する。\nなんかlibelf-devがないよ的なことを言われている。\n[uzimihsr@localhost ~]$ cat /var/log/vboxadd-setup.log Building the main Guest Additions 6.1.32 module for kernel 4.18.0-365.el8.x86_64. Error building the module. Build output follows. make V=1 CONFIG_MODULE_SIG= CONFIG_MODULE_SIG_ALL= -C /lib/modules/4.18.0-365.el8.x86_64/build M=/tmp/vbox.0 SRCROOT=/tmp/vbox.0 -j1 modules Makefile:985: *** \u0026#34;Cannot generate ORC metadata for CONFIG_UNWINDER_ORC=y, please install libelf-dev, libelf-devel or elfutils-libelf-devel\u0026#34;. Stop. make: *** [/tmp/vbox.0/Makefile-footer.gmk:117: vboxguest] Error 2 modprobe vboxguest failed libelf-develはELFバイナリファイルをいじるためのユーティリティらしい(あんまり知らない)。\nhttps://sourceware.org/elfutils/\nyum searchかけた感じだとelfutils-libelf-develしか見つからなかったのでこれを入れる。\nsudo yum install -y elfutils-libelf-devel 再度実行。\n今度は行けたっぽい？\n[uzimihsr@localhost ~]$ sudo /sbin/rcvboxadd setup VirtualBox Guest Additions: Starting. VirtualBox Guest Additions: Building the VirtualBox Guest Additions kernel modules. This may take a while. VirtualBox Guest Additions: To build modules for other installed kernels, run VirtualBox Guest Additions: /sbin/rcvboxadd quicksetup \u0026lt;version\u0026gt; VirtualBox Guest Additions: or VirtualBox Guest Additions: /sbin/rcvboxadd quicksetup all VirtualBox Guest Additions: Building the modules for kernel 4.18.0-365.el8.x86_64. VirtualBox Guest Additions: Look at /var/log/vboxadd-setup.log to find out what went wrong ValueError: File context for /opt/VBoxGuestAdditions-6.1.32/other/mount.vboxsf already defined VirtualBox Guest Additions: Running kernel modules will not be replaced until the system is restarted [uzimihsr@localhost ~]$ echo $? 0 さっきと同じログファイルにはヘッダファイルが見つからない的なエラーが出てるんだけど、\n終了コードが0だしモジュールを読み込むために再起動するよう言われてるのでそれに従う。\n[uzimihsr@localhost ~]$ cat /var/log/vboxadd-setup.log Building the main Guest Additions 6.1.32 module for kernel 4.18.0-365.el8.x86_64. Building the shared folder support module. Building the graphics driver module. Error building the module. Build output follows. make V=1 CONFIG_MODULE_SIG= CONFIG_MODULE_SIG_ALL= -C /lib/modules/4.18.0-365.el8.x86_64/build M=/tmp/vbox.0 SRCROOT=/tmp/vbox.0 -j1 modules test -e include/generated/autoconf.h -a -e include/config/auto.conf || (\t\\ echo \u0026gt;\u0026amp;2;\t\\ echo \u0026gt;\u0026amp;2 \u0026#34; ERROR: Kernel configuration is invalid.\u0026#34;;\t\\ echo \u0026gt;\u0026amp;2 \u0026#34; include/generated/autoconf.h or include/config/auto.conf are missing.\u0026#34;;\\ echo \u0026gt;\u0026amp;2 \u0026#34; Run \u0026#39;make oldconfig \u0026amp;\u0026amp; make prepare\u0026#39; on kernel src to fix it.\u0026#34;;\t\\ echo \u0026gt;\u0026amp;2 ;\t\\ /bin/false) mkdir -p /tmp/vbox.0/.tmp_versions ; rm -f /tmp/vbox.0/.tmp_versions/* make -f ./scripts/Makefile.build obj=/tmp/vbox.0 (cat /dev/null; echo kernel//tmp/vbox.0/vboxvideo.ko;) \u0026gt; /tmp/vbox.0/modules.order gcc -Wp,-MD,/tmp/vbox.0/.hgsmi_base.o.d -nostdinc -isystem /usr/lib/gcc/x86_64-redhat-linux/8/include -I./arch/x86/include -I./arch/x86/include/generated -I./include/drm-backport -I./include -I./arch/x86/include/uapi -I./arch/x86/include/generated/uapi -I./include/uapi -I./include/generated/uapi -include ./include/linux/kconfig.h -include ./include/linux/compiler_types.h -D__KERNEL__ -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -fshort-wchar -Werror-implicit-function-declaration -Wno-format-security -std=gnu89 -fno-PIE -DCC_HAVE_ASM_GOTO -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -mno-avx -m64 -falign-jumps=1 -falign-loops=1 -mno-80387 -mno-fp-ret-in-387 -mpreferred-stack-boundary=3 -mskip-rax-setup -mtune=generic -mno-red-zone -mcmodel=kernel -funit-at-a-time -DCONFIG_AS_CFI=1 -DCONFIG_AS_CFI_SIGNAL_FRAME=1 -DCONFIG_AS_CFI_SECTIONS=1 -DCONFIG_AS_SSSE3=1 -DCONFIG_AS_CRC32=1 -DCONFIG_AS_AVX=1 -DCONFIG_AS_AVX2=1 -DCONFIG_AS_AVX512=1 -DCONFIG_AS_SHA1_NI=1 -DCONFIG_AS_SHA256_NI=1 -DCONFIG_TPAUSE=1 -pipe -Wno-sign-compare -fno-asynchronous-unwind-tables -mindirect-branch=thunk-extern -mindirect-branch-register -fno-jump-tables -fno-delete-null-pointer-checks -Wno-frame-address -Wno-format-truncation -Wno-format-overflow -Wno-int-in-bool-context -O2 --param=allow-store-data-races=0 -Wframe-larger-than=2048 -fstack-protector-strong -Wno-unused-but-set-variable -Wno-unused-const-variable -g -gdwarf-4 -pg -mrecord-mcount -mfentry -DCC_USING_FENTRY -fno-inline-functions-called-once -Wdeclaration-after-statement -Wno-pointer-sign -Wno-stringop-truncation -Wno-maybe-uninitialized -fno-strict-overflow -fno-merge-all-constants -fmerge-constants -fno-stack-check -fconserve-stack -Werror=implicit-int -Werror=strict-prototypes -Werror=date-time -Werror=incompatible-pointer-types -Werror=designated-init -fmacro-prefix-map=./= -Wno-packed-not-aligned -I./include -I/tmp/vbox.0/ -I./include/drm -D__KERNEL__ -DMODULE -DRT_WITHOUT_PRAGMA_ONCE -DRT_ARCH_AMD64 -DMODULE -DKBUILD_BASENAME=\u0026#39;\u0026#34;hgsmi_base\u0026#34;\u0026#39; -DKBUILD_MODNAME=\u0026#39;\u0026#34;vboxvideo\u0026#34;\u0026#39; -c -o /tmp/vbox.0/.tmp_hgsmi_base.o /tmp/vbox.0/hgsmi_base.c In file included from /tmp/vbox.0/hgsmi_base.c:27: /tmp/vbox.0/vbox_drv.h:175:11: fatal error: drm/ttm/ttm_memory.h: No such file or directory # include \u0026lt;drm/ttm/ttm_memory.h\u0026gt; ^~~~~~~~~~~~~~~~~~~~~~ compilation terminated. make[2]: *** [scripts/Makefile.build:316: /tmp/vbox.0/hgsmi_base.o] Error 1 make[1]: *** [Makefile:1577: _module_/tmp/vbox.0] Error 2 make: *** [/tmp/vbox.0/Makefile-footer.gmk:117: vboxvideo] Error 2 [uzimihsr@localhost ~]$ sudo reboot 再起動後はたしかにマウス操作がやりやすくなっている。\n最後にクリップボード共有の設定を入れる。\nVirtualBoxの画面で\u0026quot;Settings\u0026rdquo;→\u0026ldquo;General\u0026rdquo;→\u0026ldquo;Advanced\u0026rdquo;→\u0026ldquo;Shared Clipboard\u0026quot;を\u0026quot;Bidirectional\u0026quot;にすると、\nホスト(Mac)とVM(CentOS)をまたいだコピペができるようになって便利。\n疲れたのでとりあえずここまで。\nおわり こういった地味な環境構築をたまにやるとたのしい。\nVirtualBox使うのが久しぶりでいろいろ詰まったが良い練習になった。\nおまけ ","date":"2022-02-24T00:00:00Z","image":"/post/2022-02-24-centos-stream-8-virtualbox-mac/sotochan.jpg","permalink":"/post/2022-02-24-centos-stream-8-virtualbox-mac/","title":"Run CentOS Stream 8 on VirtualBox(Intel Mac)"},{"content":"Summary A queue \u0026amp; multiple workers in golang We can use buffered channel as a queue, and goroutines as workers. WaitGroup is useful to wait for goroutines to finish. Context makes it easy to stop other goroutines from one goroutine. Prerequisites go version go1.17.3 darwin/amd64 Channel as a queue, Goroutines as workers I wanted to write the following pattern in golang:\nA queue has multiple processing targets Each worker gets one target from the queue, processes it, and repeats the procedure until the queue is empty All workers run in parallel If an error occurs in one worker, all other workers will stop after processing the current target I wrote that pattern using buffered channels and goroutines.\nHere are some notes.\nA queue has multiple processing targets Each worker gets one target from the queue, processes it, and repeats the procedure until the queue is empty In this case, we can use buffered channel as a queue.\nA worker tries to get the target from the channel, and processes it if it succeeds, or exits if the channel is closed.\nI implemented this pattern with select statement.\n// example target := []int{1, 8, 5, 2, 3, 9, 1} queue := make(chan int, len(target)) for len(target) \u0026gt; 0 { queue \u0026lt;- target[0] target = target[1:] } close(queue) for { select { case v, ok := \u0026lt;-queue: if !ok { return } // any task you want to do for v default: time.Sleep(1 * time.Second) } } If an error occurs in one worker, all other workers will stop after processing the current target context.WithCancel is suitable for this purpose.\nIf the process returns an error, the worker will execute cancelFunc.\ncancelFunc closes ctx(channel) and the worker will stop when ctx is closed.\n// example ctx, cancel := context.WithCancel(context.Background()) for { select { case \u0026lt;-ctx.Done(): return default: } err := process() // any task you want if err != nil { cancel() } } All workers run in parallel With sync.WaitGroup, it can wait for multiple goroutines to stop.\nWhen a goroutine starts, call Add to increase the counter, and when it finishes, call Done to decrease the counter.\nWait waits until the counter reaches zero.\n// example var wg sync.WaitGroup workers := 3 for i := 0; i \u0026lt; workers; i++ { wg.Add(1) go func() { defer wg.Done() // any task you want }() } wg.Wait() In order to vary the processing time depending on the target, I created the following process.\nsleep for the specified number of seconds if the specified number is 4, raise an error func process(n int) error { time.Sleep(time.Duration(n) * time.Second) if n == 4 { return errors.New(\u0026#34;Unlucky 4!!!\u0026#34;) } return nil } Then run main.go.\nGo Playground\ntargets: [1 8 5 2 3 7 6] number of workers: 3 Worker-1: target 1 START Worker-0: target 5 START Worker-2: target 8 START Worker-1: target 1 END Worker-1: target 2 START Worker-1: target 2 END Worker-1: target 3 START Worker-0: target 5 END Worker-0: target 7 START Worker-1: target 3 END Worker-1: target 6 START Worker-2: target 8 END Worker-2: no target...finish Worker-0: target 7 END Worker-0: no target...finish Worker-1: target 6 END Worker-1: no target...finish All workers finished. All targets were processed. It is hard to understand how it worked in text, so I made a diagram below.\nAll workers work in parallel and each worker gets the target from the queue.\nWorker 1 drew a small number and immediately went for the next target.\nIn contrast, Worker 2 drew a large number, and the queue had been already empty when the worker finished its first target.\nChange the target to cause an error, and run main.go again.\nGo Playground\ntargets: [1 8 4 2 3 7 6] number of workers: 3 Worker-2: target 1 START Worker-0: target 8 START Worker-1: target 4 START Worker-2: target 1 END Worker-2: target 2 START Worker-2: target 2 END Worker-2: target 3 START Worker-1: ERROR! Unlucky 4!!! Worker-2: target 3 END Worker-2: received ctx.Done()...finish Worker-0: target 8 END Worker-0: received ctx.Done()...finish All workers finished. 7 was not processed... 6 was not processed... Worker 1 drew \u0026ldquo;4\u0026rdquo; and raised an error, then all workers stopped after finishing the target they had.\n(The following is the same content in Japanese.)\nまとめ Goで複数の処理対象を1つのキューに詰めて複数のワーカーで並行して処理するやつを書いた channelをキューとして、goroutineをワーカーとして使えそう 複数goroutineの完了を待つにはWaitGroupを使うのが良さげ goroutineから他のgoroutineを止めたりするのはContextを使うのが良さげ 環境 go version go1.17.3 darwin/amd64 キュー(channel)とワーカー(go routine) Goでちょっとしたバッチ的なものを書こうとしたときに、\n同じような処理を繰り返す部分があったのでgoroutineを使っていい感じの並行処理を書きたくなった。\nイメージとしては\n処理したい対象が1つのキューに詰められている 1つのワーカーはキューから対象を1つ取り出して処理、をキューが空になるまで繰り返す すべてのワーカーは並行して動作する どこかのワーカーでエラーが発生したら他の全てのワーカーにも知らせて、それを知った各ワーカーは現在処理している対象までで処理を終了する という感じ。\n今回はかんたんなものの例としてGoのchannelとgoroutineを用いて実現してみた。\n重要そうなところを(数ヶ月後の自分のために)メモしておく。\n処理したい対象が1つのキューに詰められている 1つのワーカーはキューから対象を1つ取り出して処理、をキューが空になるまで繰り返す は次のように書いた。\n今回の用途のキューであれば、バッファありchannelを使って実現できる。\nselectを使って、channelから値が取り出せれば何らかの処理を実行、channelが閉じられたらそこで止まるようにした。\ntarget := []int{1, 8, 5, 2, 3, 9, 1} queue := make(chan int, len(target)) for len(target) \u0026gt; 0 { queue \u0026lt;- target[0] target = target[1:] } close(queue) for { select { case v, ok := \u0026lt;-queue: if !ok { return } // any task you want to do for the v default: time.Sleep(1 * time.Second) } } どこかのワーカーでエラーが発生したら他の全てのワーカーにも知らせて、それを知った各ワーカーは現在処理している対象までで処理を終了する はcontext.WithCancelを使って次のように書いた。\nワーカー(go routine)でなにかエラーが起こったらcancelFuncを実行してctx(channel )を閉じ、\nctxが閉じられたらその時点でワーカーを終了させる。\nctx, cancel := context.WithCancel(context.Background()) for { select { case \u0026lt;-ctx.Done(): return default: } err := process() // any task you want if err != nil { cancel() } } すべてのワーカーは並行して動作する ワーカーを複数並行して動かし、全部のワーカーが止まるまで待つような処理はsync.WaitGroupをつかって書いた。\nWaitを呼び出すと待つ対象が0になるまで待ち続けるので、\n各goroutineの開始時にAddして待つ対象を増やし、goroutineが終了したらDoneで待つ対象をへらすようにしている。\nvar wg sync.WaitGroup workers := 3 for i := 0; i \u0026lt; workers; i++ { wg.Add(1) go func() { defer wg.Done() // any task you want }() } wg.Wait() あとはこれらをいい感じに組み合わせた。\nまた、今回は処理対象ごとに処理時間をバラバラにしたかったので、ワーカーの処理として次のようなものを用意した。\n引数の秒数ぶん待機する 引数が4ならエラーを返す func process(n int) error { time.Sleep(time.Duration(n) * time.Second) if n == 4 { return errors.New(\u0026#34;Unlucky 4!!!\u0026#34;) } return nil } main.goを実際に動かしてみる。\nGo Playground\ntargets: [1 8 5 2 3 7 6] number of workers: 3 Worker-1: target 1 START Worker-0: target 5 START Worker-2: target 8 START Worker-1: target 1 END Worker-1: target 2 START Worker-1: target 2 END Worker-1: target 3 START Worker-0: target 5 END Worker-0: target 7 START Worker-1: target 3 END Worker-1: target 6 START Worker-2: target 8 END Worker-2: no target...finish Worker-0: target 7 END Worker-0: no target...finish Worker-1: target 6 END Worker-1: no target...finish All workers finished. All targets were processed. 狙い通りに動いたのだが、文字だけ追っても見づらいので図にしてみた。\n各ワーカーが1つずつ対象をキューから取り出してその秒数ぶん待機、を繰り返しており、\nWorker 1のように小さい数字(=処理が軽い)を引くとその分次の対象を取りに行くのが早くなっていて、\n逆にWorker 2のように最初に大きい数字(=処理が重い)を引くとその間に他のワーカーが働くので対象がなくなっている。\n最後にワーカーがエラーを吐くような処理対象をキューに仕込んで動かしてみる。\nGo Playground\ntargets: [1 8 4 2 3 7 6] number of workers: 3 Worker-2: target 1 START Worker-0: target 8 START Worker-1: target 4 START Worker-2: target 1 END Worker-2: target 2 START Worker-2: target 2 END Worker-2: target 3 START Worker-1: ERROR! Unlucky 4!!! Worker-2: target 3 END Worker-2: received ctx.Done()...finish Worker-0: target 8 END Worker-0: received ctx.Done()...finish All workers finished. 7 was not processed... 6 was not processed... こちらも狙い通り動いていて、\nこの場合は4を引いたWorker 1の処理でエラーが発生し、\n他のワーカーもそのタイミングで持っている対象を最後に以降の処理を行わないようになっている。\nおわり 久しぶりにGoにガッツリ触った。\ngo routineとかchannelとか適当な理解で済ませていたもののおさらいができてよかった。\nおまけ ","date":"2022-02-10T00:00:00Z","image":"/post/2022-02-10-queue-and-multiple-workers-golang/sotochan.jpeg","permalink":"/post/2022-02-10-queue-and-multiple-workers-golang/","title":"A queue and multiple workers in golang"},{"content":"まとめ あまえんぼ あばれんぼ あまえんぼ またもあまえんぼ期が再来！\nねこはあったかいねぇ pic.twitter.com/UtkGEckq0O\n\u0026mdash; ずみし (@uzimihsr) January 4, 2022 ソファで座ってると高確率で膝に乗ってくる上に、\n一度乗っちゃうと全然降りなくてこまっちゃう(たすかる)。\n降りない pic.twitter.com/7r9sWkOUGX\n\u0026mdash; ずみし (@uzimihsr) January 10, 2022 人の膝の上に乗ってきて勝手に暴れたりひっくり返ったり(?)やりたい放題。\n自由 pic.twitter.com/Gz2gO7BHCs\n\u0026mdash; ずみし (@uzimihsr) January 8, 2022 自由 pic.twitter.com/UJmr2WYKGK\n\u0026mdash; ずみし (@uzimihsr) January 2, 2022 たまたまねこがおしりをこっちに向けたときになんとなくぽんぽん叩いたらそれ以降ハマったらしく、\n最近はちゃんとぽんぽんしないと文句を言われる。\nこの体勢になったらねこのおしりをぽんぽんしなくてはならない pic.twitter.com/a5q8jw1A0h\n\u0026mdash; ずみし (@uzimihsr) January 27, 2022 膝に乗ってないときもぽんぽんしてほしいらしい。\nおしりぽんぽん要求ちゃん pic.twitter.com/dk5iVzAbq0\n\u0026mdash; ずみし (@uzimihsr) January 23, 2022 あまえんぼすぎてちょっと心配になるくらいだけど、\n甘えてる最中のねこちゃんがアホみたいにかわいいのでOKです。\nあまえんぼガチ勢 pic.twitter.com/CtFjYfP6TV\n\u0026mdash; ずみし (@uzimihsr) January 28, 2022 あばれんぼ 昨年末の手術後に動けなかった時期がなにかのきっかけになってしまったのか、\nそとちゃんは未だに家の中を暴れまわっている。\n暴れ毛玉 pic.twitter.com/K6Gede8pTy\n\u0026mdash; ずみし (@uzimihsr) January 12, 2022 特に早朝のテンションが最高潮で、毎朝鳴くわ跳ぶわ壊すわでとてもにぎやか。\n今朝は大暴れで家壊れると思った pic.twitter.com/oLiWUdgIAe\n\u0026mdash; ずみし (@uzimihsr) January 27, 2022 破壊 pic.twitter.com/LDHDq1c9YH\n\u0026mdash; ずみし (@uzimihsr) January 28, 2022 前の日の夜に多めに遊ぶとか、そんな程度で朝のそとちゃんは止められない。\n日が昇れば窓際で外にいかせろと叫び、\nカリカリマシンから朝ごはんが出ればドカ食いしてすぐトイレ。\nうんちをもりもり出したらちゃんと大声で報告した後(重要らしい)、俺が寝ている布団の上を走り回る。\n俺はその衝撃で一瞬だけ起きて仕方なくおもちゃを廊下に投げ、ねこはそれを追いかけてしばき続ける。\nあとはだいたい1hくらいキャットタワーと台所で暴れまわったらようやくちょっと眠くなって、\n寝ている俺のお腹の上でおしりをこっちに向けて寝始める。\nこれが我が家のモーニングルーティーン。\n起きられない pic.twitter.com/AXnYCvPTYx\n\u0026mdash; ずみし (@uzimihsr) January 25, 2022 起床封じ pic.twitter.com/abKAgMqR2O\n\u0026mdash; ずみし (@uzimihsr) January 30, 2022 一度この体勢になるとねこが自分で起きてどこかにいくまで動くことは許されない。\nおかげで朝は高確率で二度寝してしまう身体になってしまった。\nでもねこちゃんが元気なのでOKです。\nおわり 年末の手術を頑張ったそとちゃんを甘やかしまくっていたら、とんでもない甘え暴れん坊になってしまった\u0026hellip;\nが、ねこは幸せになるほどふてぶてしくなるとどこかで聞いた気がするので、これでいいかなとも思っている。\nおまけ ","date":"2022-02-01T00:00:00Z","image":"/post/2022-02-01-sotochan/sotochan.jpeg","permalink":"/post/2022-02-01-sotochan/","title":"1月のそとちゃん(2022)"},{"content":"Summary We can use the expression \u0026lt;Resource\u0026gt;.\u0026lt;API Group\u0026gt; to specify the API Group with kubectl.\n# Example: the expressions of \u0026#34;Event v1 core\u0026#34; and \u0026#34;Event v1 events.k8s.io\u0026#34; kubectl auth can-i get events kubectl auth can-i get events.events.k8s.io Prerequisites Kubernetes API v1.21.1 kubectl v1.21.5 Same resource, different API Groups In Kubernetes API 1.21, there are some different API Groups for the same kind of resource.\nFor example:\nEvent Event v1 events.k8s.io Event v1 core OLD API VERSIONS # in Kubernetes 1.21, there are two different API Groups for Events resource $ kubectl api-resources | grep event events ev v1 true Event events ev events.k8s.io/v1 true Event But if we specify \u0026ldquo;events\u0026rdquo; resource with kubectl 1.21, it will be treated as \u0026ldquo;Event v1 core\u0026rdquo;.\n# create a serviceaccount with permissions to view only \u0026#34;Event v1 core\u0026#34; $ kubectl -n default create serviceaccount user01 $ kubectl -n default create role event-corev1-reader --verb=\u0026#34;get,list\u0026#34; --resource=\u0026#34;events\u0026#34; $ kubectl -n default create rolebinding event-corev1-reader --role=event-corev1-reader --serviceaccount=default:user01 # Check the permissions of the serviceaccount # https://kubernetes.io/docs/tasks/run-application/access-api-from-pod/#without-using-a-proxy $ kubectl -n default run nginx --image=nginx --restart=Never --rm -it --overrides=\u0026#39;{ \u0026#34;spec\u0026#34;: { \u0026#34;serviceAccountName\u0026#34; : \u0026#34;user01\u0026#34; } }\u0026#39; -- /bin/bash # The serviceaccount can list \u0026#34;Event v1 core\u0026#34; (root@nginx)$ curl -I -X GET -k https://kubernetes.default.svc/api/v1/namespaces/default/events -H \u0026#34;Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)\u0026#34; HTTP/2 200 ... # The serviceaccount is not allowed to list \u0026#34;Event v1 events.k8s.io\u0026#34; (root@nginx)$ curl -I -X GET -k https://kubernetes.default.svc/apis/events.k8s.io/v1/namespaces/default/events -H \u0026#34;Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)\u0026#34; HTTP/2 403 ... So, how can we specify the API Group \u0026ldquo;events.k8s.io\u0026rdquo; with kubectl?\nAccording to the help, we can use the expression \u0026lt;Resource\u0026gt;.\u0026lt;API Group\u0026gt;.\n$ kubectl create role --help ... Usage: kubectl create role NAME --verb=verb --resource=resource.group/subresource [--resource-name=resourcename] [--dry-run=server|client|none] [options] ... This expression can be used with other kubectl subcommands such as kubectl auth can-i.\n# create a serviceaccount with permissions to view only \u0026#34;Event v1 events.k8s.io\u0026#34; $ kubectl -n default create serviceaccount user02 $ kubectl -n default create role event-eventsk8siov1-reader --verb=\u0026#34;get,list\u0026#34; --resource=\u0026#34;events.events.k8s.io\u0026#34; $ kubectl -n default create rolebinding event-eventsk8siov1-reader --role=event-eventsk8siov1-reader --serviceaccount=default:user02 # user01 can view only \u0026#34;Event v1 core\u0026#34; $ kubectl -n default auth can-i get events --as system:serviceaccount:default:user01 yes $ kubectl -n default auth can-i get events.events.k8s.io --as system:serviceaccount:default:user01 no # user02 can view only \u0026#34;Event v1 events.k8s.io\u0026#34; $ kubectl -n default auth can-i get events --as system:serviceaccount:default:user02 no $ kubectl -n default auth can-i get events.events.k8s.io --as system:serviceaccount:default:user02 yes (The following is the same content in Japanese.)\nまとめ リソースが同じでもAPI Groupが異なるものをkubectlで指定したいときは\u0026lt;Resource\u0026gt;.\u0026lt;API Group\u0026gt;とすれば良い。\n# \u0026#34;Event v1 core\u0026#34;と\u0026#34;Event v1 events.k8s.io\u0026#34;をそれぞれ指定して権限を確認する例 kubectl auth can-i get events kubectl auth can-i get events.events.k8s.io 環境 Kubernetes API v1.21.1 kubectl v1.21.5 同じリソース名でAPI versionが複数あるとき Kubernetes APIではv1.21時点でEventのAPI Groupがevents.k8s.ioになっていて、\nそれまであったcore版のEventはOLD API VERSIONSという扱い。\n# 1.22時点では2種類のAPI Groupが存在する $ kubectl api-resources | grep event events ev v1 true Event events ev events.k8s.io/v1 true Event しかし、v1.22のkubectlで普通にeventsに対するRoleを作成するとAPI Groupがcoreで指定されてしまう。\n# \u0026#34;event\u0026#34;を指定してRoleを作成する $ kubectl -n default create serviceaccount user01 $ kubectl -n default create role event-corev1-reader --verb=\u0026#34;get,list\u0026#34; --resource=\u0026#34;events\u0026#34; $ kubectl -n default create rolebinding event-corev1-reader --role=event-corev1-reader --serviceaccount=default:user01 # Podを立てて確認してみる # https://kubernetes.io/docs/tasks/run-application/access-api-from-pod/#without-using-a-proxy $ kubectl -n default run nginx --image=nginx --restart=Never --rm -it --overrides=\u0026#39;{ \u0026#34;spec\u0026#34;: { \u0026#34;serviceAccountName\u0026#34; : \u0026#34;user01\u0026#34; } }\u0026#39; -- /bin/bash # coreのAPIは利用可能 (root@nginx)$ curl -I -X GET -k https://kubernetes.default.svc/api/v1/namespaces/default/events -H \u0026#34;Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)\u0026#34; HTTP/2 200 ... # events.k8s.ioのAPIは認可エラーで利用不可 (root@nginx)$ curl -I -X GET -k https://kubernetes.default.svc/apis/events.k8s.io/v1/namespaces/default/events -H \u0026#34;Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)\u0026#34; HTTP/2 403 ... じゃあk8s.events.io版への権限はどうやって付与するんだろう?と試行錯誤していたら、helpにちゃんと書いてあった\u0026hellip;\n$ kubectl create role --help ... Usage: kubectl create role NAME --verb=verb --resource=resource.group/subresource [--resource-name=resourcename] [--dry-run=server|client|none] [options] ... リソースの後に続いて.\u0026lt;API Group\u0026gt;を指定すればいいらしい。\n(何も指定しない場合はcoreになるっぽい?)\nこの方法で再度Roleを指定しなおす。\n$ kubectl -n default create serviceaccount user02 $ kubectl -n default create role event-eventsk8siov1-reader --verb=\u0026#34;get,list\u0026#34; --resource=\u0026#34;events.events.k8s.io\u0026#34; $ kubectl -n default create rolebinding event-eventsk8siov1-reader --role=event-eventsk8siov1-reader --serviceaccount=default:user02 他のコマンドでリソースを指定するときも同様にできるらしいので、今度はPodを建てずにkubectl authで確認してみる。\n# user01: core版のみの閲覧権限 $ kubectl -n default auth can-i get events --as system:serviceaccount:default:user01 yes $ kubectl -n default auth can-i get events.events.k8s.io --as system:serviceaccount:default:user01 no # user02: events.k8s.io版のみの閲覧権限 $ kubectl -n default auth can-i get events --as system:serviceaccount:default:user02 no $ kubectl -n default auth can-i get events.events.k8s.io --as system:serviceaccount:default:user02 yes やったぜ。\n異なるAPI Groupをkubectlで指定できた。\nおわり ちゃんとヘルプを読んでいればなんてことない話だったんだけど、\n自分は数時間ムダにしてしまったのでメモにした。\nやっぱり公式ドキュメントやヘルプを読むのは大事。\nおまけ ","date":"2022-01-20T00:00:00Z","image":"/post/2022-01-20-kubectl-specify-api-group/sotochan.jpg","permalink":"/post/2022-01-20-kubectl-specify-api-group/","title":"Specify the API Group of the resource with kubectl"},{"content":"まとめ 手術 術後 がまん 抜糸 手術 今月は首にできた腫瘤の手術があった。\n手術当日のそとちゃんはいつもどおり余裕ぶっこき状態。\nそれまで毎日そとちゃんと手術について(日本語で)話してはいたけど、\n結局自分がなにをされるのかは最後まで理解していないようだった。\n(それはそう😭)\n御守りを食べるねこ pic.twitter.com/Bhp7rYojVW\n\u0026mdash; ずみし (@uzimihsr) December 16, 2021 余裕ぶっこいてる pic.twitter.com/JZpgtiQZoN\n\u0026mdash; ずみし (@uzimihsr) December 17, 2021 今回は日帰り手術ということで、\n朝に診察を行い、昼は検査と手術、術後の様子を診て夕方に引き取りという流れ。\nまずは診察。\nこれはそとちゃんというより俺のための時間という感じだった。\n当日のそとちゃんの健康状態(食欲や遊ぶ元気)について軽く聞かれた後、\n全身麻酔に関する説明と手術についての詳細、術後に気をつけるべきことなどについてもう一度丁寧に説明してもらい、\n最後に承諾書のサインと押印を確認した。\n一通りの説明を受けた後、最後にそとちゃんを少し抱きしめてから先生に預けた。\n万が一のことがあればこれが最後の別れになるかもしれないと思って、恥ずかしながらこのとき少し泣いてしまった。😢\n(ここからは手術前後に先生から説明を受けた内容)\n次に検査。\n全身麻酔には当然ながらリスクがあるので、\n事故の可能性を最低限に抑えるために必ず術前に血液検査とX線検査が行われる。\n先生によるとそとちゃんは注射は平気だったものの、X線検査で仰向けにされたときにめちゃくちゃ暴れたらしい。\n(普段ひたすらごろごろしてるくせに、強制されるのは嫌😓)\n検査結果に問題がなかったので麻酔をかけて施術が行われた。\n切開から縫合までは十数分で終了し、その後そとちゃんはゆっくり目を醒ました。\n手術の間はずっと心配でソワソワしていたので、術後に電話で連絡が来たときは本当に安心した。😭\n術後 電話をもらった後も念のために病院で少し様子を診てもらい、\n夕方ごろにそとちゃんを引き取りに行った。\n診察室で5時間ぶりに対面したそとちゃんは少し不機嫌だった。😡\n引き取りの直前におしっこを漏らしてしまったようで、\n看護師さんにおしりを拭かれたのが気にいらなかったらしい。\nその場で先生から血液検査とX線検査の結果を説明してもらった。\n血液検査の数値はおおむね問題なしだが、クレアチニンとグロブリンの値が少し高め グロブリンは免疫が活発なときに高い数値になるもので、おそらく猫風邪の後遺症(右目の目ヤニができやすい原因)によるものだろうから仕方ない クレアチニンは腎臓の機能が低下したときに高くなる値なので、3ヶ月後を目安に再検査が必要😱 X線検査では肺や内臓に問題なく、その他の腫瘤などもない 切除した腫瘤は病理検査に送って1週間後に結果が返ってくるとのことで、\n順調にいけばそのタイミングで抜糸できるかも?とのことだった。\nちなみに手術代の見積もりで病理検査の費用が漏れていたらしく、実際の請求額はかなり高くついてしまった\u0026hellip;😭\n病院から家に帰った後のそとちゃんは流石に少しぐったりしていたものの、ちょっとしてすぐにわんわん騒ぎ出した。\n手術のタイミングで胃を空っぽにする都合で前日の夜から何も食べさせていなかったので、ほぼ丸一日ぶんお腹を空かせていたのだった。😭\n手術痕から感染症にかからないための抗生剤(かなり苦い)をすりつぶしてちゅーるに混ぜてあげたところ、ものすごい勢いで完食。\n術後は弱っていると思っていたが食欲があるようで安心した。🙏\nちゅーる(抗生物質入り)\n爆速 pic.twitter.com/B32HKTD6IA\n\u0026mdash; ずみし (@uzimihsr) December 17, 2021 がまん 手術後は我慢の1週間だった。\nそとちゃんは元気なのに、手術痕を守るためのカラーが邪魔でうまく遊べない。😢\nカラーをつけた状態でできる遊びにもだんだん飽きてしまって、結局ほとんど寝て過ごしていた。\nいっぱい寝ててえらい pic.twitter.com/9yw10506Wr\n\u0026mdash; ずみし (@uzimihsr) December 19, 2021 あとはトイレが大変だった。\nいつもはトイレの後自分でおしりをなめてきれいにできるのに、カラーのせいで舌が届かず汚れたままになっていることが何度かあった。\nウエットタオルで拭くようにしていたが、そとちゃんはやっぱりおしりを拭かれるのが嫌いなのでちょっとかわいそうだった。😭\n一番大変だったのはかゆみとの戦い。\n手術痕がカラーに微妙に当たる位置で、かゆいのに舌で舐められない様子は見ていてつらそうだった。\n後ろ脚の爪をカラーの隙間から無理やり入れて手術痕を掻こうとすることもあったので、なかなか目が話せなかった\u0026hellip;\nそとちゃんがずっと苦しい思いをしていたので、俺もこの1週間はほぼ家から出ずにつきっきりでみていた。\nつくづく在宅勤務でよかった。\n抜糸 1週間耐え抜いた後、腫瘤の検査結果が届いたとのことで再度病院へ。\n抜糸できるか診てもらいにいく\nいつもよりビビってる pic.twitter.com/SPE3hkiZLG\n\u0026mdash; ずみし (@uzimihsr) December 25, 2021 病理検査の結果、今回切除した腫瘤は毛包嚢胞ということで、\n人間で言えばニキビとか粉瘤に近いもの?らしかった。\n悪性腫瘍でなくて本当によかった\u0026hellip;😌\n完全に切除されていて再発のおそれも低いということなので、これで一安心。\n手術痕もきれいにふさがっていたので、その場で抜糸してもらった。\n手術と検査で病院が嫌になっちゃったのか、そとちゃんは珍しく暴れたけどなんとか無事に糸を全部外すことができた。\n帰宅後、カラーが外れて7日ぶりの自由を取り戻したそとちゃんはやりたい放題。😂\n抜糸成功 pic.twitter.com/qm0q0U2PbS\n\u0026mdash; ずみし (@uzimihsr) December 25, 2021 カラーが邪魔でなかなか登れなかったキャットタワーや本棚の上に昇って降りてを繰り返し、\n部屋中をものすごい勢いで走り回って手がつけられなかった。🌪\nちなみにカラーがなくなって手術痕(ふさがってはいるが\u0026hellip;)を思いっきり掻こうとしたので、\nこの後カラーをつけて外してを何回か繰り返した。😅\nおわり なにはともあれそとちゃんが無事で本当によかった。\n手術前はごはんをもらえず、手術後はカラーをつけられてかわいそうだったがよく耐えてとてもえらかった。👏\n術後の生活が大変すぎて、気がついたらクリスマスが終わっていた\u0026hellip;\n去年と違う衣装も用意していたが、手術後に着せるわけにもいかないので来年まで残しておくことにする。\n今年もそとちゃんはずっと元気だったけど、最後に手術があって大変だった。\n2022年はそとちゃんの健康により一層気をつけて、幸せに過ごせるようにしたい。\nおまけ ","date":"2021-12-30T00:00:00Z","image":"/post/2021-12-31-sotochan/sotochan.jpeg","permalink":"/post/2021-12-31-sotochan/","title":"12月のそとちゃん(2021)"},{"content":"(最後に日本語版があります)\ncronjob-name labels admission webhook Kubernetes Pods owned by Job have a job-name label, so you can easily filter Pods by the name of the owner Job with label selector.\nMeanwhile, Jobs owned by CronJob don\u0026rsquo;t have such one.\n# filter Pods by Job with labelSelector $ kubectl get pod -l job-name=my-job NAME READY STATUS RESTARTS AGE my-job-zm4p4 0/1 Completed 0 25h # Jobs do not have any labels related to the owner CronJob $ kubectl get job --show-labels NAME COMPLETIONS DURATION AGE LABELS my-cronjob-27322002 1/1 2s 2m26s controller-uid=43e62299-838b-4f9a-96b7-e35cfc82a2ec,job-name=my-cronjob-27322002 my-cronjob-27322003 1/1 2s 86s controller-uid=56130637-ae62-42f4-b8f5-f5248c929ec0,job-name=my-cronjob-27322003 my-cronjob-27322004 1/1 3s 26s controller-uid=308090e7-2bdb-4960-bd38-ff88b3c0aaf0,job-name=my-cronjob-27322004 my-job 1/1 3s 25h controller-uid=3369540c-552b-438c-8767-8808d9c42fe6,job-name=my-job Of course, Jobs also have a field called OwnerReferences which has the name of the owner CronJob, but it can\u0026rsquo;t be used for filtering Jobs.\n# metadata.ownerReferences have the name of the owner CronJob $ kubectl get job my-cronjob-27322004 -o jsonpath=\u0026#34;{.metadata.ownerReferences[0].name}\u0026#34; my-cronjob # ownerReferences cannot be used to filter Job $ kubectl get job --field-selector \u0026#34;.metadata.ownerReferences[0].name=my-cronjob\u0026#34; Error from server (BadRequest): Unable to find \u0026#34;batch/v1, Resource=jobs\u0026#34; that match label selector \u0026#34;\u0026#34;, field selector \u0026#34;.metadata.ownerReferences[0].name=my-cronjob\u0026#34;: field label \u0026#34;.metadata.ownerReferences[0].name\u0026#34; not supported for Job Therefore, when there are many Jobs in the Cluster, it is difficult to find the Jobs by the name of the owner CronJob.\nOf course you can use grep or jq with kubectl, but it is sometimes not fast :(\nTo solve this problem, I created my own Mutating Admission Webhook for the first time.\nhttps://github.com/uzimihsr/cronjob-name-labels-admission-webhook\n# Jobs have a cronjob-name label and it can be used for filtering $ kubectl get job -l uzimihsr.github.io/cronjob-name=my-cronjob --show-labels NAME COMPLETIONS DURATION AGE LABELS my-cronjob-27322012 1/1 2s 2m26s uzimihsr.github.io/cronjob-name=my-cronjob my-cronjob-27322013 1/1 2s 86s uzimihsr.github.io/cronjob-name=my-cronjob my-cronjob-27322014 1/1 2s 26s uzimihsr.github.io/cronjob-name=my-cronjob Prerequisites Kubernetes v1.21.1 kind v0.11.1 Go 1.17.3 OpenSSL 3.0.0 How it works Mutating Admission Webhook works as an HTTP server that returns responses to admission requests and we can make it with any language.\nIn this case, I used the webhook example written in Go.\nThis HTTP server works as follows;\nConvert the request body to admission.k8s.io/v1.AdmissionReview. If the requested resource(Job) is owned by any CronJob, create a JSON Patch to add the label \u0026ldquo;uzimihsr.github.io/cronjob-name={Job.metadata.ownerReferences[0].name}\u0026rdquo;. You must escape / in a JSON key. Convert the JSON Patch to admission.k8s.io/v1.AdmissionReview and return it. The HTTP server can be located anywhere as long as it is reachable from the Kubernetes API, but I preferred to run it as Deployment in the cluster.\nThe TLS Secret is needed because the admission webhook requires a TLS connection between the Kubernetes API and the HTTP server.\nIf the HTTP server runs in the cluster, self-signed certificate is sufficient.\nThe ClusterIP Service exposes the Pod to the cluster.\nIt is referenced as \u0026quot;(service-name).(service-namespace).svc” from within the cluster, so the certificate of the TLS Secret must have that name as SANs.\nMutatingWebhookConfiguration allows the Kubernetes API to send admission requests to the HTTP server.\nIt contains the conditions to send webhooks and base64-encoded CA certificate of the self-signed certificate.\nHow to install The container image and manifest files are available on GitHub.\nhttps://github.com/uzimihsr/cronjob-name-labels-admission-webhook#install\n# generate a private key openssl genrsa -out tls.key # create a self-signed certificate # Common Name (e.g. server FQDN or YOUR name) []:cronjob-name-labels-admission-webhook.default.svc (if you run Pod in the default namespace) openssl req -x509 -key tls.key -out tls.crt -days 3650 -addext \u0026#39;subjectAltName = DNS:cronjob-name-labels-admission-webhook.default.svc\u0026#39; # create a TLS Secret kubectl create secret tls cronjob-name-labels-admission-webhook-tls-secret --cert=tls.crt --key=tls.key # create Deployment, Service, and MutatingWebhookConfiguration # replace \u0026#34;CA_BUNDLE\u0026#34; in MutatingWebhookConfiguration by base64-encoded tls.crt curl https://raw.githubusercontent.com/uzimihsr/cronjob-name-labels-admission-webhook/main/manifests/manifest.yaml \\ | sed \u0026#34;s/CA_BUNDLE/$(kubectl get secret cronjob-name-labels-admission-webhook-tls-secret -o jsonpath=\u0026#39;{.data.tls\\.crt}\u0026#39;)/g\u0026#34; \\ | kubectl apply -f - Once all the resources have been successfully created and the Pod is running, create some CronJobs to verify the operation.\n# create some CronJobs kubectl create cronjob cronjob-a --image=busybox --schedule=\u0026#34;*/1 * * * *\u0026#34; -- date kubectl create cronjob cronjob-b --image=busybox --schedule=\u0026#34;*/1 * * * *\u0026#34; -- date kubectl create cronjob cronjob-c --image=busybox --schedule=\u0026#34;*/1 * * * *\u0026#34; -- date # create a \u0026#34;normal\u0026#34; Job (not owned by CronJob) kubectl create job my-job --image=busybox -- date Wait for while and check the Jobs.\n# Jobs owned by CronJob have a cronjob-name label # The \u0026#34;normal\u0026#34; Job does not have the label $ kubectl get job --show-labels NAME COMPLETIONS DURATION AGE LABELS cronjob-a-27322073 1/1 6s 2m23s uzimihsr.github.io/cronjob-name=cronjob-a cronjob-a-27322074 1/1 5s 83s uzimihsr.github.io/cronjob-name=cronjob-a cronjob-a-27322075 1/1 5s 23s uzimihsr.github.io/cronjob-name=cronjob-a cronjob-b-27322073 1/1 2s 2m23s uzimihsr.github.io/cronjob-name=cronjob-b cronjob-b-27322074 1/1 4s 83s uzimihsr.github.io/cronjob-name=cronjob-b cronjob-b-27322075 1/1 3s 23s uzimihsr.github.io/cronjob-name=cronjob-b cronjob-c-27322073 1/1 4s 2m23s uzimihsr.github.io/cronjob-name=cronjob-c cronjob-c-27322074 1/1 2s 83s uzimihsr.github.io/cronjob-name=cronjob-c cronjob-c-27322075 1/1 6s 23s uzimihsr.github.io/cronjob-name=cronjob-c my-job 1/1 8s 23s controller-uid=27160b7f-65e0-4e48-aa6b-9319e162f422,job-name=my-job # Wow you can filter Jobs by CronJob :) $ kubectl get job -l uzimihsr.github.io/cronjob-name=cronjob-b NAME COMPLETIONS DURATION AGE cronjob-b-27322073 1/1 2s 2m39s cronjob-b-27322074 1/1 4s 99s cronjob-b-27322075 1/1 3s 39s I did it. It\u0026rsquo;s working!\nFinally, let\u0026rsquo;s compare the speed of filtering Jobs by the name of the CronJob with other methods.\n# create 100 CronJobs for i in $(seq -w 100); do kubectl create cronjob cronjob-\u0026#34;${i}\u0026#34; --image=busybox --schedule=\u0026#34;*/1 * * * *\u0026#34; -- date done ## look for Jobs owned by the CronJob \u0026#34;cronjob-099\u0026#34; in different ways # kubectl + grep: 0.277(kubectl) + 0.276(grep) ≃ 0.5 sec. $ time kubectl get job | grep cronjob-099 cronjob-099-27322103 1/1 2m29s 6m33s cronjob-099-27322104 1/1 5m35s 5m41s cronjob-099-27322105 0/1 4m39s 4m39s cronjob-099-27322106 0/1 3m55s 3m55s cronjob-099-27322107 0/1 2m39s 2m39s cronjob-099-27322108 0/1 109s 109s cronjob-099-27322109 0/1 41s 42s kubectl get job 0.25s user 0.04s system 105% cpu 0.277 total grep --color=auto cronjob-099 0.00s user 0.00s system 2% cpu 0.276 total # kubectl + jq: 0.642(kubectl) + 0.648(jq) ≃ 1.2 sec. $ time kubectl get job -o json | jq \u0026#39;.items[] | select(.metadata.ownerReferences[]?.name==\u0026#34;cronjob-099\u0026#34;) | .metadata.name\u0026#39; \u0026#34;cronjob-099-27322103\u0026#34; \u0026#34;cronjob-099-27322104\u0026#34; \u0026#34;cronjob-099-27322105\u0026#34; \u0026#34;cronjob-099-27322106\u0026#34; \u0026#34;cronjob-099-27322107\u0026#34; \u0026#34;cronjob-099-27322108\u0026#34; \u0026#34;cronjob-099-27322109\u0026#34; kubectl get job -o json 0.70s user 0.06s system 117% cpu 0.642 total jq 0.08s user 0.01s system 13% cpu 0.648 total # Label Selector \u0026#34;uzimihsr.github.io/cronjob-name\u0026#34; : ≃ 0.1 sec. $ time kubectl get job -l uzimihsr.github.io/cronjob-name=cronjob-099 NAME COMPLETIONS DURATION AGE cronjob-099-27322103 1/1 2m29s 6m40s cronjob-099-27322104 1/1 5m35s 5m48s cronjob-099-27322105 0/1 4m46s 4m46s cronjob-099-27322106 0/1 4m2s 4m2s cronjob-099-27322107 0/1 2m46s 2m46s cronjob-099-27322108 0/1 116s 116s cronjob-099-27322109 0/1 48s 49s cronjob-099-27322110 0/1 1s 1s kubectl get job -l uzimihsr.github.io/cronjob-name=cronjob-099 0.08s user 0.03s system 113% cpu 0.094 total Wow the label added by Mutating Admission Webhook makes it so fast to filter Jobs by the name of owner CronJob!\nThank you for reading :)\n(the following is the Japanese version.)\nつくったもの KubernetesのJobで起動したPodにはjob-nameというラベルがついている。\nこれにより、\u0026ldquo;hogehogeというJobで起動されているPodの一覧が欲しい\u0026quot;といったときはKubernetes APIへのリクエスト時にlabelSelectorを指定することで絞り込みができる。\n# job-nameラベルを用いたPodの絞り込み $ kubectl get pod -l job-name=my-job NAME READY STATUS RESTARTS AGE my-job-zm4p4 0/1 Completed 0 25h しかし、CronJobによって起動されたJobにはそのようなラベルがなく、\n\u0026ldquo;fugafugaというCronJobで起動されているJobの一覧が欲しい\u0026quot;というときに不便。\n# JobにはCronJob名のラベルがない $ kubectl get job --show-labels NAME COMPLETIONS DURATION AGE LABELS my-cronjob-27322002 1/1 2s 2m26s controller-uid=43e62299-838b-4f9a-96b7-e35cfc82a2ec,job-name=my-cronjob-27322002 my-cronjob-27322003 1/1 2s 86s controller-uid=56130637-ae62-42f4-b8f5-f5248c929ec0,job-name=my-cronjob-27322003 my-cronjob-27322004 1/1 3s 26s controller-uid=308090e7-2bdb-4960-bd38-ff88b3c0aaf0,job-name=my-cronjob-27322004 my-job 1/1 3s 25h controller-uid=3369540c-552b-438c-8767-8808d9c42fe6,job-name=my-job 一応Jobはmetadata.ownerReferencesというフィールドにどのCronJobによって作成されたかの情報を持っているのだが、\nこのフィールドはfieldSelectorに対応していないので少し使いづらい。\n# metadata.ownerReferencesには一応CronJobの情報がある $ kubectl get job my-cronjob-27322004 -o jsonpath=\u0026#34;{.metadata.ownerReferences[0].name}\u0026#34; my-cronjob # fieldSelectorで指定しようとするとエラーになる $ kubectl get job --field-selector \u0026#34;.metadata.ownerReferences[0].name=my-cronjob\u0026#34; Error from server (BadRequest): Unable to find \u0026#34;batch/v1, Resource=jobs\u0026#34; that match label selector \u0026#34;\u0026#34;, field selector \u0026#34;.metadata.ownerReferences[0].name=my-cronjob\u0026#34;: field label \u0026#34;.metadata.ownerReferences[0].name\u0026#34; not supported for Job このため、CronJobを大量に使っているときに調べたい対象のJobを絞るのが面倒で困っていた。\n\u0026hellip;だったら自分でcronjob-name的なラベルをつけちゃえば良いのでは? と思い、\n最近勉強しているadmission webhookを使って実現してみた。\nhttps://github.com/uzimihsr/cronjob-name-labels-admission-webhook\n# JobにCronJob名のラベルがついたのでlabelSelectorで絞り込める $ kubectl get job -l uzimihsr.github.io/cronjob-name=my-cronjob --show-labels NAME COMPLETIONS DURATION AGE LABELS my-cronjob-27322012 1/1 2s 2m26s uzimihsr.github.io/cronjob-name=my-cronjob my-cronjob-27322013 1/1 2s 86s uzimihsr.github.io/cronjob-name=my-cronjob my-cronjob-27322014 1/1 2s 26s uzimihsr.github.io/cronjob-name=my-cronjob 環境 Kubernetes v1.21.1 kind v0.11.1 Go 1.17.3 OpenSSL 3.0.0 しくみ 今回は下記のような構成でつくってみた。\nまずは決められた形式のリクエストに対してレスポンスを返すHTTPサーバーをDeploymentとして立てる。\nMutating Admission Webhookでラベルを付与する処理はGoで書かれた公式の例を参考にした。\nこのサーバーの処理の流れとしては\nリクエストボディをadmission.k8s.io/v1.AdmissionReviewにパース リクエストされているリソース(Job)のObjectをチェックし、対象のリソースがCronJobによって作成されている場合のみ \u0026ldquo;uzimihsr.github.io/cronjob-name=(CronJob名)\u0026rdquo; のラベルを付与するJSON Patchを作成 今回はラベルのキーに/が含まれるので、keyの指定方法に気をつけた 作成したJSONパッチをadmission.k8s.io/v1.AdmissionReviewに詰めて返す といった感じ。\nまた、admission webhookでは対象のエンドポイントとの通信がTLS化されている必要があるので、\nオレオレ証明書のTLS Secretを用意してPodにマウントして使用する。\nDeploymentが作成できたら、そのPodをClusterIP Serviceでクラスタ内に公開する。\nこのときクラスタ内から参照するホスト名が \u0026quot;(service-name).(service-namespace).svc\u0026rdquo; となるので、前述のTLS SecretのTLS証明書のSANsがこの名前を含む必要がある。\n最後にMutatingWebhookConfigurationを作成すると、\nKubernetes APIが指定されたAPIオブジェクトを操作するときに指定されたService宛にwebhookするようになる。\n今回はオレオレ証明書を使用しているので、信頼するCA証明書(すなわちオレオレ証明書そのもの)をbase64エンコードしたものをwebhooks.clientConfig.caBundleに指定する。\nつかいかた Deployment用のimageと各種yamlファイルはGitHubに用意しているので、\nあとはTLS証明書用のSecretさえ用意すれば動くはず。\nhttps://github.com/uzimihsr/cronjob-name-labels-admission-webhook#install\n# 秘密鍵の作成 openssl genrsa -out tls.key # オレオレ証明書の作成 # Common Name (e.g. server FQDN or YOUR name) []:cronjob-name-labels-admission-webhook.default.svc とする openssl req -x509 -key tls.key -out tls.crt -days 3650 -addext \u0026#39;subjectAltName = DNS:cronjob-name-labels-admission-webhook.default.svc\u0026#39; # TLS Secretの作成 kubectl create secret tls cronjob-name-labels-admission-webhook-tls-secret --cert=tls.crt --key=tls.key # Deployment, Service, MutatingWebhookConfigurationの作成 # MutatingWebhookConfigurationのcaBundlerはbase64化したtls.crtを指定する curl https://raw.githubusercontent.com/uzimihsr/cronjob-name-labels-admission-webhook/main/manifests/manifest.yaml \\ | sed \u0026#34;s/CA_BUNDLE/$(kubectl get secret cronjob-name-labels-admission-webhook-tls-secret -o jsonpath=\u0026#39;{.data.tls\\.crt}\u0026#39;)/g\u0026#34; \\ | kubectl apply -f - すべてのリソースが作成できたら、動作確認用のCronJobを作成する。\n# CronJobの作成 kubectl create cronjob cronjob-a --image=busybox --schedule=\u0026#34;*/1 * * * *\u0026#34; -- date kubectl create cronjob cronjob-b --image=busybox --schedule=\u0026#34;*/1 * * * *\u0026#34; -- date kubectl create cronjob cronjob-c --image=busybox --schedule=\u0026#34;*/1 * * * *\u0026#34; -- date # CronじゃないJobの作成 kubectl create job my-job --image=busybox -- date すこし放置して、Jobが起動したら結果を確認する。\n# JobにCronJob名のラベルがついている # 手動で作ったJobのラベルはデフォルトのまま $ kubectl get job --show-labels NAME COMPLETIONS DURATION AGE LABELS cronjob-a-27322073 1/1 6s 2m23s uzimihsr.github.io/cronjob-name=cronjob-a cronjob-a-27322074 1/1 5s 83s uzimihsr.github.io/cronjob-name=cronjob-a cronjob-a-27322075 1/1 5s 23s uzimihsr.github.io/cronjob-name=cronjob-a cronjob-b-27322073 1/1 2s 2m23s uzimihsr.github.io/cronjob-name=cronjob-b cronjob-b-27322074 1/1 4s 83s uzimihsr.github.io/cronjob-name=cronjob-b cronjob-b-27322075 1/1 3s 23s uzimihsr.github.io/cronjob-name=cronjob-b cronjob-c-27322073 1/1 4s 2m23s uzimihsr.github.io/cronjob-name=cronjob-c cronjob-c-27322074 1/1 2s 83s uzimihsr.github.io/cronjob-name=cronjob-c cronjob-c-27322075 1/1 6s 23s uzimihsr.github.io/cronjob-name=cronjob-c my-job 1/1 8s 23s controller-uid=27160b7f-65e0-4e48-aa6b-9319e162f422,job-name=my-job # labelSelectorを使った絞り込みができる $ kubectl get job -l uzimihsr.github.io/cronjob-name=cronjob-b NAME COMPLETIONS DURATION AGE cronjob-b-27322073 1/1 2s 2m39s cronjob-b-27322074 1/1 4s 99s cronjob-b-27322075 1/1 3s 39s CronJobから作成されたJobに所望のラベルが追加されていて、ラベルを用いた絞り込みができることを確認できた。\n最後に、CronJobから作られたJobを絞り込む際の速度を他の方法と比較してみる。\n# CronJobが100個稼働している状態 for i in $(seq -w 100); do kubectl create cronjob cronjob-\u0026#34;${i}\u0026#34; --image=busybox --schedule=\u0026#34;*/1 * * * *\u0026#34; -- date done ## 色んな方法でcronjob-099のJobを探してみる # 愚直にgrepするとkubectlで0.277+grepで0.276==約0.5秒かかる $ time kubectl get job | grep cronjob-099 cronjob-099-27322103 1/1 2m29s 6m33s cronjob-099-27322104 1/1 5m35s 5m41s cronjob-099-27322105 0/1 4m39s 4m39s cronjob-099-27322106 0/1 3m55s 3m55s cronjob-099-27322107 0/1 2m39s 2m39s cronjob-099-27322108 0/1 109s 109s cronjob-099-27322109 0/1 41s 42s kubectl get job 0.25s user 0.04s system 105% cpu 0.277 total grep --color=auto cronjob-099 0.00s user 0.00s system 2% cpu 0.276 total # jqとの組み合わせ技だとkubectlで0.642+jqで0.648=約1.2秒... $ time kubectl get job -o json | jq \u0026#39;.items[] | select(.metadata.ownerReferences[]?.name==\u0026#34;cronjob-099\u0026#34;) | .metadata.name\u0026#39; \u0026#34;cronjob-099-27322103\u0026#34; \u0026#34;cronjob-099-27322104\u0026#34; \u0026#34;cronjob-099-27322105\u0026#34; \u0026#34;cronjob-099-27322106\u0026#34; \u0026#34;cronjob-099-27322107\u0026#34; \u0026#34;cronjob-099-27322108\u0026#34; \u0026#34;cronjob-099-27322109\u0026#34; kubectl get job -o json 0.70s user 0.06s system 117% cpu 0.642 total jq 0.08s user 0.01s system 13% cpu 0.648 total # webhookで付与したラベルを使うと約0.1秒で済む $ time kubectl get job -l uzimihsr.github.io/cronjob-name=cronjob-099 NAME COMPLETIONS DURATION AGE cronjob-099-27322103 1/1 2m29s 6m40s cronjob-099-27322104 1/1 5m35s 5m48s cronjob-099-27322105 0/1 4m46s 4m46s cronjob-099-27322106 0/1 4m2s 4m2s cronjob-099-27322107 0/1 2m46s 2m46s cronjob-099-27322108 0/1 116s 116s cronjob-099-27322109 0/1 48s 49s cronjob-099-27322110 0/1 1s 1s kubectl get job -l uzimihsr.github.io/cronjob-name=cronjob-099 0.08s user 0.03s system 113% cpu 0.094 total ラベルをつけたおかげでCronJobのJobがそこそこ速く絞り込めるようになった。\nやったぜ。\nおわり admission webhookを自分で作って動かしてみた。\nいい勉強になったし、自分で使う上でもそこそこ便利なものができたと思っている。\nカッコつけてそれっぽくリポジトリをつくったものの、まだテストが書けてなかったりするので暇なときにちょくちょく修正していきたい。\nおまけ ","date":"2021-12-18T00:00:00Z","image":"/post/2021-12-18-kubernetes-mutating-admission-webhook-add-label-to-job/sotochan.jpg","permalink":"/post/2021-12-18-kubernetes-mutating-admission-webhook-add-label-to-job/","title":"Add a cronjob-name label to Kubernetes Jobs with Mutating Admission Webhook"},{"content":"まとめ ひざ上占拠ちゃん 箱がアツい 病院 ひざ上占拠ちゃん 通算n度目のひざブームがきた。\n俺がソファでだらだらしてるとだいたい乗っかってくる。\n乗り pic.twitter.com/Zozm0vMcf1\n\u0026mdash; ずみし (@uzimihsr) November 14, 2021 そとちゃんに乗られるとねこを撫でる/スマホをいじる以外の行動が封じられてとても幸せ。\nただし少しでも脚が動けば機嫌を損ねてしまう\u0026hellip;\n勝手に乗ってきてイカ耳されるの納得いかない pic.twitter.com/lvRsdEwgpl\n\u0026mdash; ずみし (@uzimihsr) November 27, 2021 乗ってきたからと言って必ずしも撫でられたいわけでもないらしい。\nむずかしい\u0026hellip;\n(ひざの上で寝返りされるとめっちゃ嬉しい)\nねこ撫でたら反撃された pic.twitter.com/0LP3CEd008\n\u0026mdash; ずみし (@uzimihsr) November 28, 2021 ソファにこだわりがある訳でもないらしく、仕事用のイスに座ってるときでも乗ってくる。\nイスの場合はちょっと高さがあるので万が一落ちたら怖いが、\nそとちゃんはそんなのお構いなしで自由に動いてしまう。\nヒトの膝の安定感を信用しすぎている(毛づくろい中) pic.twitter.com/ZvyIcN0NCC\n\u0026mdash; ずみし (@uzimihsr) November 16, 2021 自由すぎて寝ちゃうときもある。\n寝た pic.twitter.com/Q4rheVsyQr\n\u0026mdash; ずみし (@uzimihsr) November 5, 2021 超かわいい。\n(なお、長いときは1時間くらいそのまま動かないので脚が死ぬ😇)\n箱がアツい 箱より袋派のそとちゃん。\n最近しもべがお高いものを買わないせいで紙袋の供給が減ってピンチ!\nとはならず、袋がないときは箱でも十分楽しめるみたい。\n横向きの箱に入って待ち構えたところに俺がねずみのおもちゃを投げてそれをしばくのが流行っている。\nこれ5回くらいやってる pic.twitter.com/f6hfKLFsLP\n\u0026mdash; ずみし (@uzimihsr) November 5, 2021 ねずみをしばく、箱に隠れる、ねずみをしばく、をエンドレスに繰り返す。\n野良だったころの狩りの動きに近くて楽しいのかも？\n横着して箱から出ずにねずみをしばくだけのパターンもある。\nゴールキーパー pic.twitter.com/NuNUXIlcQT\n\u0026mdash; ずみし (@uzimihsr) November 15, 2021 逆に箱に入っていくねずみを追いかけてしばくときもある。\n結局横向きの箱とねずみがあれば何でも楽しいらしい。\n箱めっちゃたのしい pic.twitter.com/tsy2BTjJES\n\u0026mdash; ずみし (@uzimihsr) November 18, 2021 今まで使ってた引っ越しダンボールはそとちゃんにはちょっと大きくて、\n最近しもべが食べ始めたnoshの宅配ダンボールがちょうどそとちゃんサイズにフィットしていてかわいい。\n横向きの箱は入る率高い pic.twitter.com/5h0KAFRbVv\n\u0026mdash; ずみし (@uzimihsr) November 15, 2021 そとちゃんも広すぎるダンボールよりはちょっと狭い方が好きみたい。\nたのしそう pic.twitter.com/QI1FYWBvx4\n\u0026mdash; ずみし (@uzimihsr) November 26, 2021 新しいダンボールにお熱なうちに古い引っ越しダンボールはこっそり捨ててしまいたい\u0026hellip;\n(たぶん無理)\n病院 そとちゃんがやたらとお出かけしたがっていたのと、\n前回見つかった首のしこりがちょっと大きくなってきた気がしたので9月以来の病院に行ってきた。\n病院いく pic.twitter.com/SlS3EaY6IG\n\u0026mdash; ずみし (@uzimihsr) November 23, 2021 触診の結果やはりしこりが大きくなっていて、\n前回はヒトの小指の先くらいのサイズだったのが今回は人差し指くらいの太さになっていた。\nその場で超音波検査をしてもらい、\n(検査のために先生に両手両足をガッツリ掴まれてブーブー文句言ってる姿がちょっとかわいそうだけど面白かった)\nエコーを診たところ水が溜まっているとかではなく固形の腫瘤ができていた。😱\n超音波検査だけではこの腫瘤が良性か悪性かは判定できないとのことで、\n先生から2つの対応を提案してもらった。\n注射針を入れて腫瘤の一部を吸い取って化学検査 外科手術での切除 前者はある程度様子見、後者は全身麻酔のリスクがある方法。\n今の所は腫瘤を触っても痛そうにする素振りがないので問題なさそうだが、\nこれが良性にしろ悪性にしろこのまま大きくなり続けると血管を圧迫する恐れがあるということで今回は腫瘤を切除してもらうことになった。\nスケジュールの都合で手術は少し先の12/17に行うことが決まり、 見積もりも出してもらった。\n自分が外科手術をしてもらったことがないので全身麻酔についてよくわかっていないのと、\nさらにそれが小さいそとちゃんのこととなると正直不安でしょうがない。\nめちゃめちゃ怖いけど、\n手術の内容について繰り返し質問しても優しく答えてくれた先生の人柄と、\n避妊手術で一度お腹を切っているそとちゃんの強さを信じることにした。\n腫瘤以外の話だと、\n下痢対策で引っ越す前の病院に勧めてもらった療法食のカリカリを食べ続けても問題ないか聞いたところ、\nこの療法食の場合は必要な栄養素が充分含まれていて、問題になる成分がないので食べ続けて問題ないとのことだった。\n手術のことで混乱していて、歯肉炎の状態を診てもらうのは忘れてしまった。\n病院大好きなそとちゃんも今回の超音波検査は嫌だったみたいで、\n帰るときはちょっと元気がなくておとなしかった。\nがんばってえらかったので家に帰ってすぐおやつをあげた。\n爪が刺さってめちゃ痛かった。\n病院がんばった pic.twitter.com/jyouQMh30d\n\u0026mdash; ずみし (@uzimihsr) November 23, 2021 おわり 最近は手術が心配で毎晩寝る前にそとちゃんに大丈夫か聞いてるけど「にゃん」しか言わない。\nたぶんなにもわかってなさそう\u0026hellip;\nとりあえず無事を祈ってお守りを買ってこようと思う。\nおまけ ","date":"2021-12-06T00:00:00Z","image":"/post/2021-12-06-sotochan/sotochan.jpeg","permalink":"/post/2021-12-06-sotochan/","title":"11月のそとちゃん(2021)"},{"content":"まとめ あんまりやる機会はないだろうけど、\nPodが生きている間にそれに紐づくNamespaceとServiceAccountを削除したときの挙動は次のようになる。\nPodが生きている間にmetadata.namespaceのNamespaceを削除した場合 Podが削除される Podが生きている間にspec.serviceAccountNameのServiceAccountを削除した場合 Podは削除されない 自動マウントされたServiceAccount Tokenはそのまま残る ただしServiceAccountが削除された時点でKubenretes APIの認証が通らなくなる 環境 kind v0.11.1 go1.17.3 darwin/amd64 Kubernetes v1.21.1 もくじ Namespace消す Serviceaccount消す Namespace消す とあるNamespaceでPodが生きているときに、kubectl delete namespaceしたらどうなるんだろう\u0026hellip;?\n# じゅんび $ kubectl create namespace space-1 $ kubectl -n space-1 create serviceaccount user-1 $ kubectl -n space-1 run nginx --image=nginx --overrides=\u0026#39;{ \u0026#34;spec\u0026#34;: { \u0026#34;serviceAccountName\u0026#34; : \u0026#34;user-1\u0026#34; } }\u0026#39; # Namespace: space-1でspec.serviceAccountName: user-1のPodが動いている状態 $ kubectl -n space-1 get pod nginx NAME READY STATUS RESTARTS AGE nginx 1/1 Running 0 15s $ kubectl -n space-1 get pod nginx -o jsonpath=\u0026#34;{.spec.serviceAccountName}\u0026#34; user-1 # Podが動いてるNamespace消す $ kubectl delete namespace space-1 namespace \u0026#34;space-1\u0026#34; deleted # どこにもPodが残ってない $ kubectl get pod -A | grep -c nginx 0 Namespaceを削除すると、動いているPodがあっても一緒に削除されるらしい。\nServiceAccount消す 今度はPodのspec.serviceAccountNameに指定されたServiceAccountをPodが生きている間に消してみる。\n# さっきと同じ状態 $ kubectl -n space-1 get pod nginx NAME READY STATUS RESTARTS AGE nginx 1/1 Running 0 15s $ kubectl -n space-1 get pod nginx -o jsonpath=\u0026#34;{.spec.serviceAccountName}\u0026#34; user-1 $ kubectl -n space-1 get serviceaccount user-1 NAME SECRETS AGE user-1 1 75s # Podに紐付いてるServiceAccount消す $ kubectl -n space-1 delete serviceaccount user-1 serviceaccount \u0026#34;user-1\u0026#34; deleted # Podは生きてる上にserviceAccountNameもそのまま $ kubectl -n space-1 get pod nginx NAME READY STATUS RESTARTS AGE nginx 1/1 Running 0 2m46s $ kubectl -n space-1 get pod nginx -o jsonpath=\u0026#34;{.spec.serviceAccountName}\u0026#34; user-1 # PodにマウントされているServiceAccount Tokenは削除したServiceAccountのものが残っている $ kubectl -n space-1 get pod nginx -o jsonpath=\u0026#34;{.spec.volumes}\u0026#34; [{\u0026#34;name\u0026#34;:\u0026#34;kube-api-access-fdd7d\u0026#34;,\u0026#34;projected\u0026#34;:{\u0026#34;defaultMode\u0026#34;:420,\u0026#34;sources\u0026#34;:[{\u0026#34;serviceAccountToken\u0026#34;:{\u0026#34;expirationSeconds\u0026#34;:3607,\u0026#34;path\u0026#34;:\u0026#34;token\u0026#34;}},{\u0026#34;configMap\u0026#34;:{\u0026#34;items\u0026#34;:[{\u0026#34;key\u0026#34;:\u0026#34;ca.crt\u0026#34;,\u0026#34;path\u0026#34;:\u0026#34;ca.crt\u0026#34;}],\u0026#34;name\u0026#34;:\u0026#34;kube-root-ca.crt\u0026#34;}},{\u0026#34;downwardAPI\u0026#34;:{\u0026#34;items\u0026#34;:[{\u0026#34;fieldRef\u0026#34;:{\u0026#34;apiVersion\u0026#34;:\u0026#34;v1\u0026#34;,\u0026#34;fieldPath\u0026#34;:\u0026#34;metadata.namespace\u0026#34;},\u0026#34;path\u0026#34;:\u0026#34;namespace\u0026#34;}]}}]}}] $ kubectl -n space-1 exec nginx -it -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | jq -R \u0026#39;split(\u0026#34;.\u0026#34;) | .[1] | @base64d | fromjson | .\u0026#34;kubernetes.io\u0026#34;\u0026#39; { \u0026#34;namespace\u0026#34;: \u0026#34;space-1\u0026#34;, \u0026#34;pod\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;nginx\u0026#34;, \u0026#34;uid\u0026#34;: \u0026#34;c1595cec-699d-41dd-bb51-6f648b23e832\u0026#34; }, \u0026#34;serviceaccount\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;user-1\u0026#34;, \u0026#34;uid\u0026#34;: \u0026#34;e1d20d3d-5f9f-4ab0-b8e0-d021a42d189c\u0026#34; }, \u0026#34;warnafter\u0026#34;: 1637856366 } なるほど\u0026hellip;?\nPodのspec.serviceAccountNameに指定されたServiceAccountが削除されてもPodは消されることはなく、\nvolumeとしてコンテナにマウントされたServiceAccount Tokenもそのままの状態で残るらしい。\nじゃあそのTokenを使ったKubernetes APIの認証/認可はどうなるんだろう?\n# またまたさっきと同じ状態 $ kubectl -n space-1 get pod nginx NAME READY STATUS RESTARTS AGE nginx 1/1 Running 0 15s $ kubectl -n space-1 get pod nginx -o jsonpath=\u0026#34;{.spec.serviceAccountName}\u0026#34; user-1 $ kubectl -n space-1 get serviceaccount user-1 NAME SECRETS AGE user-1 1 75s # user-1にspace-1のPod操作権限を付与 $ kubectl -n space-1 create role pod-owner --verb=\u0026#34;*\u0026#34; --resource=\u0026#34;pods\u0026#34; $ kubectl -n space-1 create rolebinding pod-owner-bind --role=pod-owner --serviceaccount=space-1:user-1 # この状態でServiceAccount Tokenを用いてKubernetes APIをたたくと成功(200 OK)する $ kubectl -n space-1 exec nginx -it -- bash -c \u0026#39;curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt --header \u0026#34;Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)\u0026#34; -I -X GET https://kubernetes.default.svc/api/v1/namespaces/space-1/pods\u0026#39; HTTP/2 200 ... # ServiceAccountを削除 $ kubectl -n space-1 delete serviceaccount user-1 # Podに残ったServiceAccount Tokenを用いてKubernetes APIをたたくと失敗(401 Unauthorized)する $ kubectl -n space-1 exec nginx -it -- bash -c \u0026#39;curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt --header \u0026#34;Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)\u0026#34; -I -X GET https://kubernetes.default.svc/api/v1/namespaces/space-1/pods\u0026#39; HTTP/2 401 ... # 同名のServiceAccountを再作成 $ kubectl -n space-1 create serviceaccount user-1 # しかし結果は変わらず $ kubectl -n space-1 exec nginx -it -- bash -c \u0026#39;curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt --header \u0026#34;Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)\u0026#34; -I -X GET https://kubernetes.default.svc/api/v1/namespaces/space-1/pods\u0026#39; HTTP/2 401 ... はえ〜\nServiceAccountが削除された時点でTokenでの認証ができなくなるみたい。\n後から同名のServiceAccountを作ってもたぶんTokenのuidが新しいServiceAccountと合わないから認証ができないんじゃないかな\u0026hellip;?\nちなみにkubectl edit podでspec.serviceAccountNameをいじろうとすると\nForbidden: pod updates may not change fields other than `spec.containers[*].image`, `spec.initContainers[*].image`, `spec.activeDeadlineSeconds` or `spec.tolerations` (only additions to existing tolerations)` って怒られちゃうので、\nPodが生きてる間にServiceAccountを消してしまった場合はそのTokenでのAPI認証は諦めてPodを作り直すのがよさそう。\n(というかPodが生きているうちはあまり消さないほうがいい)\nおわり こんなことは普段仕事でKubernetesを運用しているときは絶対やらないとは思うけど、すこし気になったので試してみた。\nNamespaceもServiceAccountもPodを巻き添えにして削除されるものだと思いこんでいたので、\nServiceAccountが消えてもPodとマウントされたTokenが残るのは勉強になった。\nおまけ ","date":"2021-11-28T00:00:00Z","image":"/post/2021-11-28-delete-serviceaccount/sotochan.jpg","permalink":"/post/2021-11-28-delete-serviceaccount/","title":"Kubernetes Podが生きている間にNamespaceとかServiceAccountを消すとどうなるの?"},{"content":"まとめ 歯みがき まるい? ハロウィン 歯みがき やっぱり歯みがきが嫌い。\nこの世の全てに絶望して引きこもった(はみがきが嫌) pic.twitter.com/OKWGazGWnw\n\u0026mdash; ずみし (@uzimihsr) October 21, 2021 9月に病院で診てもらってからは薬を塗らない日も毎日歯みがきをするようになったんだけど、\n歯肉炎になってる右の奥歯に触れられるのがどうしても嫌みたい。\nふだんはやさしくて滅多に怒らないそとちゃんでもこのときだけは手加減なしで暴れてくるのでひどいときは(俺が)流血沙汰になる。\nそれでも最後は抵抗虚しく捕まえられて歯みがきと塗り薬をやられて拗ねちゃう。\n抵抗むなしく歯みがきされて拗ねてる pic.twitter.com/CPtY0LJqGZ\n\u0026mdash; ずみし (@uzimihsr) October 17, 2021 (あんまり怒り慣れてないからいじけ方もぎこちなくてちょっとかわいい)\n歯肉炎の塗り薬は処方されたぶんをしっかり使い終わって、9月のはじめよりもちょっと赤みが引いたような気もする。\n近いうちにまた病院で診てもらいたい。　まるい? ちょっと寒くなってきたのか、そとちゃんがまるくなっていることが増えた。\nまるい pic.twitter.com/kppQydVKzb\n\u0026mdash; ずみし (@uzimihsr) October 1, 2021 まるくなった pic.twitter.com/tfgERaqHAN\n\u0026mdash; ずみし (@uzimihsr) October 6, 2021 でもまるくないときもある。\nまるくない pic.twitter.com/FaIo4SFUkd\n\u0026mdash; ずみし (@uzimihsr) October 6, 2021 ぴーぴー言いながら寝てる pic.twitter.com/rCTVPBzljc\n\u0026mdash; ずみし (@uzimihsr) October 28, 2021 \u0026hellip;というかまるくないことのほうが多いかもしれない。\n最近まるくないことのほうが多い pic.twitter.com/Z88hN28lPA\n\u0026mdash; ずみし (@uzimihsr) October 8, 2021 たまに四角いときもある。\nもはやちょっと四角い pic.twitter.com/Lq1cEji2c8\n\u0026mdash; ずみし (@uzimihsr) October 28, 2021 毎日おもしろい寝かたをしているので見ていて飽きない。\nかわいい。\nハロウィン 今年もそとちゃんにハロウィンの仮装をしてもらった。\nドラキュラマント#HappyHalloween2021 pic.twitter.com/DxTVkM6xLI\n\u0026mdash; ずみし (@uzimihsr) October 31, 2021 例年(嫌々ながら)着てもらっていたドラキュラマントに加えて、今年はこうもりポンチョもご用意させていただいた。\nこれがまたバチクソにかわいい。\nこうもりポンチョ#HappyHalloween2021 pic.twitter.com/u3r6HBFzU2\n\u0026mdash; ずみし (@uzimihsr) October 31, 2021 本当は小型犬用の服なんだけど、頭からかぶってボタンで留めるだけなのでそとちゃんでも着られた。\n(手を通すタイプだとめちゃくちゃ嫌がる)\nでも最後は自分で脱いじゃった。\n脱ぐ pic.twitter.com/eP6mv2Heuz\n\u0026mdash; ずみし (@uzimihsr) November 1, 2021 逆にドラキュラマントはそんなに気にならないみたいで、脱がずにずっとウロウロしていた。\n(着たままトイレしようとしたので流石に脱がせた)\n俺のわがままに付き合ってもらったので、最後はちゃんと報酬を支払った。\n(耳がでてくるとこが最高にかわいい)\nおやつ pic.twitter.com/x56SDNygRO\n\u0026mdash; ずみし (@uzimihsr) November 1, 2021 クリスマスもまたかわいい服を着てもらいたい。\nおわり そとちゃんは10月も元気によく遊んでよく寝るいい子だった。\n歯みがきでどれだけ不機嫌になっても絶対に\u0026quot;シャーッ!\u0026ldquo;とか\u0026quot;フーッ!\u0026ldquo;って怒ることがないので(暴れはする)、\nそとちゃんが保護されるまで関わってきた周りのねこもみんな優しい性格で、そとちゃんは怒りの表現を知らないまま成猫になったのかな\u0026hellip;と思ったりもした。\nそろそろ本格的に寒くなってくるころなので、暖かくして幸せに過ごせるようにしたい。\nおまけ ","date":"2021-11-08T00:00:00Z","image":"/post/2021-11-08-sotochan/sotochan.jpeg","permalink":"/post/2021-11-08-sotochan/","title":"10月のそとちゃん(2021)"},{"content":"やったことのまとめ 自分は普段マネージドKubernetesばかり使っていて、当たり前のようにIngressを使ってアプリをクラスタ外に公開している。\nでもよくよく考えるとIngressが使えるのはクラスタの管理者がIngress Controllerを用意してくれているからで、\n今までそれ自体を自分で設定したことはなかった。\n仕事でこれだけKubernetes触ってるのにIngress Controllerを設定したことがないのは流石に恥ずかしいと思い、\nkubeadmで作った自前のクラスタにNGINX Ingress Controllerを設定して、サービスがクラスタ外に公開できるところまで試した。\nkubeadmで作った自前クラスタにingress-nginx(NGINX Ingress Controller)を入れてNodePort Serviceで動かした Ingress Classの設定をした MetalLBを使ってingress-nginxをLoadBalancer Serviceで動かした つかうもの Ubuntu 20.04.3 LTS (GNU/Linux 5.11.0-1020-gcp x86_64) containerd 1.4.11 Calico v3.20.2 Kubernetes v1.22.2 NGINX Ingress controller v1.0.4 MetalLB 0.10.3 やったこと 自前クラスタの作成 ingress-nginxのインストール(NodePort) 動作確認(NodePort) MetalLBのインストール ingress-nginxのインストール(with MetalLB) 動作確認(with MetalLB) 自前クラスタの作成 前回作ったクラスタをそのまま使う。\n$ kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME kubernetes-master Ready control-plane,master 5m56s v1.22.2 10.240.0.2 \u0026lt;none\u0026gt; Ubuntu 20.04.3 LTS 5.11.0-1020-gcp containerd://1.4.11 kubernetes-worker Ready \u0026lt;none\u0026gt; 3m30s v1.22.2 10.240.0.3 \u0026lt;none\u0026gt; Ubuntu 20.04.3 LTS 5.11.0-1020-gcp containerd://1.4.11 $ kubectl get deploy -A NAMESPACE NAME READY UP-TO-DATE AVAILABLE AGE calico-apiserver calico-apiserver 1/1 1 1 5m15s calico-system calico-kube-controllers 1/1 1 1 6m16s calico-system calico-typha 2/2 2 2 6m17s kube-system coredns 2/2 2 2 7m27s tigera-operator tigera-operator 1/1 1 1 6m24s $ kubectl get ds -A NAMESPACE NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE calico-system calico-node 2 2 2 2 2 kubernetes.io/os=linux 6m36s kube-system kube-proxy 2 2 2 2 2 kubernetes.io/os=linux 7m46s $ kubectl get service -A NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE calico-apiserver calico-api ClusterIP 10.96.0.75 \u0026lt;none\u0026gt; 443/TCP 6m2s calico-system calico-kube-controllers-metrics ClusterIP 10.97.190.112 \u0026lt;none\u0026gt; 9094/TCP 6m27s calico-system calico-typha ClusterIP 10.97.47.173 \u0026lt;none\u0026gt; 5473/TCP 7m4s default kubernetes ClusterIP 10.96.0.1 \u0026lt;none\u0026gt; 443/TCP 8m16s kube-system kube-dns ClusterIP 10.96.0.10 \u0026lt;none\u0026gt; 53/UDP,53/TCP,9153/TCP 8m14s $ kubectl get ingress -A No resources found ingress-nginxのインストール(NodePort) Ingress Controllerにはいくつか種類があるが、Kubernetes公式でサポートされている中ではNGINX Ingress Controllerが自前クラスタに使えそう。\n公式の手順に従ってインストールしてみる。\n# https://kubernetes.github.io/ingress-nginx/deploy/#bare-metal kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.0.4/deploy/static/provider/baremetal/deploy.yaml 今回の手順だとクラスタ外のロードバランサーを用意していないので、Ingress Controller自体がNodePort Serviceでクラスタ外からのリクエストを受けてIngressのルールに則ったトラフィックの振り分けを行うらしい。\n# Ingress ControllerのDeploymentが立っている $ kubectl -n ingress-nginx get deploy NAME READY UP-TO-DATE AVAILABLE AGE ingress-nginx-controller 1/1 1 1 7m34s $ kubectl -n ingress-nginx get pods --field-selector=status.phase=Running -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES ingress-nginx-controller-644555766d-4pwm2 1/1 Running 0 7m8s 192.168.231.195 kubernetes-worker \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; $ kubectl -n ingress-nginx exec -it ingress-nginx-controller-644555766d-4pwm2 -- /nginx-ingress-controller --version ------------------------------------------------------------------------------- NGINX Ingress controller Release: v1.0.4 Build: 9b78b6c197b48116243922170875af4aa752ee59 Repository: https://github.com/kubernetes/ingress-nginx nginx version: nginx/1.19.9 ------------------------------------------------------------------------------- # Ingress Controller自体がNodePort Serviceで公開されている $ kubectl -n ingress-nginx get service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ingress-nginx-controller NodePort 10.104.81.185 \u0026lt;none\u0026gt; 80:31862/TCP,443:30703/TCP 8m29s ingress-nginx-controller-admission ClusterIP 10.98.56.107 \u0026lt;none\u0026gt; 443/TCP 8m29s $ kubectl -n ingress-nginx describe service ingress-nginx-controller Name: ingress-nginx-controller Namespace: ingress-nginx Labels: app.kubernetes.io/component=controller app.kubernetes.io/instance=ingress-nginx app.kubernetes.io/managed-by=Helm app.kubernetes.io/name=ingress-nginx app.kubernetes.io/version=1.0.4 helm.sh/chart=ingress-nginx-4.0.6 Annotations: \u0026lt;none\u0026gt; Selector: app.kubernetes.io/component=controller,app.kubernetes.io/instance=ingress-nginx,app.kubernetes.io/name=ingress-nginx Type: NodePort IP Family Policy: SingleStack IP Families: IPv4 IP: 10.104.81.185 IPs: 10.104.81.185 Port: http 80/TCP TargetPort: http/TCP NodePort: http 31862/TCP Endpoints: 192.168.231.195:80 Port: https 443/TCP TargetPort: https/TCP NodePort: https 30703/TCP Endpoints: 192.168.231.195:443 Session Affinity: None External Traffic Policy: Cluster Events: \u0026lt;none\u0026gt; 動作確認(NodePort) Ingress Controllerが用意できたので、適当なPodとIngressを用意して動作を確認する。\n手順はKubernetes公式のものを一部改変して試した。\n# 動作確認用Deployment, Service, Ingressの作成 kubectl create deployment web --image=gcr.io/google-samples/hello-app:1.0 --port=8080 kubectl expose deployment web --port=8080 kubectl create ingress example-ingress --rule=\u0026#34;hello-world.info/*=web:8080\u0026#34; 作成したリソースはこんな感じ。\n$ kubectl get pods -l app=web -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES web-7c884bf475-tt9g6 1/1 Running 0 8m21s 192.168.231.196 kubernetes-worker \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; $ kubectl get service web -o wide NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR web ClusterIP 10.99.167.187 \u0026lt;none\u0026gt; 8080/TCP 6m7s app=web # ちょっとあやしい...? $ kubectl describe ingress example-ingress Name: example-ingress Namespace: default Address: Default backend: default-http-backend:80 (\u0026lt;error: endpoints \u0026#34;default-http-backend\u0026#34; not found\u0026gt;) Rules: Host Path Backends ---- ---- -------- hello-world.info / web:8080 (192.168.231.196:8080) Annotations: \u0026lt;none\u0026gt; Events: \u0026lt;none\u0026gt; 試しにmaster nodeからクラスタを介さずIngress経由で疎通できるか試してみる。\nインストール後に確認したようにIngress Controller自体がNodePort Serviceで公開されているので、そこにリクエストを飛ばす。\n# \u0026lt;nodeのIP\u0026gt;:\u0026lt;NodePort\u0026gt;にリクエストしたけどダメ... $ curl -H \u0026#34;Host: hello-world.info\u0026#34; 10.240.0.3:31862/ \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;404 Not Found\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;center\u0026gt;\u0026lt;h1\u0026gt;404 Not Found\u0026lt;/h1\u0026gt;\u0026lt;/center\u0026gt; \u0026lt;hr\u0026gt;\u0026lt;center\u0026gt;nginx\u0026lt;/center\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; は？失敗したんだが😭\nどうやら作ったIngressにIngress Classの指定がないのでIngress Controllerに認識されてないっぽい。\n(1クラスタに複数のIngress Controllerが存在することがあるので、Ingress Classが指定されていないといろいろ問題になる)\n対応方法としては\nIngress作成時にspec.ingressClassNameフィールドを指定する Ingress Classにアノテーションをつけてデフォルト設定にする のどちらかだと思う。 今回はデフォルトIngress Classを設定する方法を試してみる。\n# Ingress Classにアノテーションをつけてデフォルト設定にする kubectl annotate ingressclass nginx ingressclass.kubernetes.io/is-default-class=true # ingressを作り直す kubectl delete ingress example-ingress kubectl create ingress example-ingress --rule=\u0026#34;hello-world.info/*=web:8080\u0026#34; # (optional)IngressClassのデフォルト設定をしない場合は--classでIngressClassを指定する kubectl create ingress example-ingress --class=nginx --rule=\u0026#34;hello-world.info/*=web:8080\u0026#34; Ingress Classの設定をした上で再度挑戦してみる。\n# アノテーションがついている $ kubectl describe ingressclass nginx Name: nginx Labels: app.kubernetes.io/component=controller app.kubernetes.io/instance=ingress-nginx app.kubernetes.io/managed-by=Helm app.kubernetes.io/name=ingress-nginx app.kubernetes.io/version=1.0.4 helm.sh/chart=ingress-nginx-4.0.6 Annotations: ingressclass.kubernetes.io/is-default-class: true Controller: k8s.io/ingress-nginx Events: \u0026lt;none\u0026gt; # さっきと違ってIngress Controllerに認識されてそう $ kubectl describe ingress example-ingress Name: example-ingress Namespace: default Address: 10.240.0.3 Default backend: default-http-backend:80 (\u0026lt;error: endpoints \u0026#34;default-http-backend\u0026#34; not found\u0026gt;) Rules: Host Path Backends ---- ---- -------- hello-world.info / web:8080 (192.168.231.196:8080) Annotations: \u0026lt;none\u0026gt; Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Sync 2m8s (x2 over 2m12s) nginx-ingress-controller Scheduled for sync # 今度は疎通できる $ curl -H \u0026#34;Host: hello-world.info\u0026#34; 10.240.0.3:31862/ Hello, world! Version: 1.0.0 Hostname: web-7c884bf475-tt9g6 今度はちゃんとIngress経由で通信できた。🎉\nやったぜ。\nMetalLBのインストール とはいえやっぱりクラスタ外からアクセスするときにデカいポートをいちいち指定するのはしんどい。\n次はNodePort ServiceではなくLoadBalancer Serviceを使って普通のポートでトラフィックを受けられるようにしてみる。\nNGINX Ingress Controllerのドキュメントによると、クラウドプロバイダで管理されていないベアメタルクラスタでもMetalLBを導入すればLoadBalancer Serviceが使えるようになるらしい。\n# NodePort版Ingress Controllerの削除 kubectl delete -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.0.4/deploy/static/provider/baremetal/deploy.yaml # MetalLBのインストール kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.10.3/manifests/namespace.yaml kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.10.3/manifests/metallb.yaml # L2 modeの設定 # https://metallb.universe.tf/configuration/#layer-2-configuration # nodeのネットワークで使われていないIPを使用する(今回は10.240.0.0/24の中でnodeに割り当てられていないIP) cat \u0026lt;\u0026lt; EOF | kubectl apply -f - apiVersion: v1 kind: ConfigMap metadata: namespace: metallb-system name: config data: config: | address-pools: - name: default protocol: layer2 addresses: - 10.240.0.10-10.240.0.15 EOF MetalLBのインストールと設定が正常にできていればこんな感じになる。\n$ k -n metallb-system get all NAME READY STATUS RESTARTS AGE pod/controller-77c44876d-wgqzj 1/1 Running 0 5m43s pod/speaker-fdcrw 1/1 Running 0 5m43s pod/speaker-kgz2w 1/1 Running 0 5m43s NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE daemonset.apps/speaker 2 2 2 2 2 kubernetes.io/os=linux 5m43s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/controller 1/1 1 1 5m43s NAME DESIRED CURRENT READY AGE replicaset.apps/controller-77c44876d 1 1 1 5m43s ingress-nginxのインストール(with MetalLB) あとはLoadBalancer Serviceを使う設定のingress-nginxをインストールする。\n# LoadBalancerになるはず kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.0.4/deploy/static/provider/cloud/deploy.yaml 外部からリクエストを受けてトラフィックを振り分けるためのIngress Controllerが今度はNodePort ServiceでなくちゃんとLoadBalancer Serviceで公開されていて、しかもExternalIPにMetalLBで指定したIPが設定されている。\n$ k -n ingress-nginx get all NAME READY STATUS RESTARTS AGE pod/ingress-nginx-admission-create--1-fbqsf 0/1 Completed 0 28s pod/ingress-nginx-admission-patch--1-l2pz5 0/1 Completed 1 28s pod/ingress-nginx-controller-5c8d66c76d-wfbjf 1/1 Running 0 29s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/ingress-nginx-controller LoadBalancer 10.111.44.72 10.240.0.10 80:31646/TCP,443:32459/TCP 29s service/ingress-nginx-controller-admission ClusterIP 10.99.244.117 \u0026lt;none\u0026gt; 443/TCP 29s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/ingress-nginx-controller 1/1 1 1 29s NAME DESIRED CURRENT READY AGE replicaset.apps/ingress-nginx-controller-5c8d66c76d 1 1 1 29s NAME COMPLETIONS DURATION AGE job.batch/ingress-nginx-admission-create 1/1 4s 29s job.batch/ingress-nginx-admission-patch 1/1 4s 28s 動作確認(with MetalLB) 最後に、同じネットワークのnodeからMetalLBで払い出されたLoadBalancer ServiceのIP越しにIngress経由のリクエストを送ってみる。\n# 払い出したExretnalIPで接続できた! $ curl -H \u0026#34;Host: hello-world.info\u0026#34; 10.240.0.10/ Hello, world! Version: 1.0.0 Hostname: web-7c884bf475-tt9g6 できた〜〜🎉\n10.240.0.10にサーバが立ってないのにリクエストができるのはMetalLBのspeakerが同じネットワークに送られたARPリクエストに対して応答するおかげらしい(まだちゃんと理解してない)。\nよくわかってないけどとりあえず普通のポートでIngressが動いたのでヨシ!\nおわり 自前のクラスタにIngress Controllerを突っ込む練習をしてみた。\n正直なところドキュメント通りにyamlをapplyするだけで簡単に設定できてしまったので本当に身についたかは怪しいが、\nIngress ClassについてはIngress Controllerを意識しないと触れる機会が少ないし、実際あまりわかってなかったので勉強になってよかった。\nMetalLBについてはまたどこかでお世話になりそうなのでそのときにちゃんと勉強したい。\nおまけ ","date":"2021-10-28T00:00:00Z","image":"/post/2021-10-28-ingress-nginx-bare-metal/sotochan.jpg","permalink":"/post/2021-10-28-ingress-nginx-bare-metal/","title":"kubeadmで作ったクラスタにNGINX Ingress Controllerを入れる"},{"content":"やったことのまとめ クラスタ操作の練習用にGKEみたいなマネージドではない自前のKubernetesクラスタがほしくて、\nかつ運用の練習としてはkindやMinikubeみたいなやつだと物足りない。\nとはいえKubernetes The Hard Wayするほどじゃないんだよな\u0026hellip;みたいな気分になったので、\nおさらいも兼ねてkubeadmでクラスタを立てることにした。\n基本的にCreating a cluster with kubeadmをなぞっているだけ。\nGCPでVMとネットワークを作成した kubeadmを使ってKubernetesクラスタをつくった コンテナランタイムにはcontainerd, ネットワークアドオンはCalicoを使用した あまり長くつかっているとお金がかかるのでおかたづけした つかうもの macOS Big Sur 11.2.3 Google Cloud SDK 360.0.0 GCP操作に使用 Ubuntu 20.04.3 LTS (GNU/Linux 5.11.0-1020-gcp x86_64) node用のVMとして使用 containerd 1.4.11 コンテナランタイム Calico v3.20.2 ネットワークアドオン Kubernetes v1.22.2 やったこと ネットワークとVMの準備(GCP) VPCネットワークの作成 VMインスタンスの作成 nodeの準備 コンテナランタイム(containerd)のインストール kubelet,kubeadm,kubectlのインストール masterの起動 ネットワークアドオン(Calico)のインストール workerの起動 おかたづけ ネットワークとVMの準備(GCP) gcloudのセットアップはmacOS用のクイックスタートで終わっている前提。\nVPCネットワークの作成 まずはnode同士をつなぐためのネットワークを設定する。\nGCPではVPCネットワークを使う。\n# Macで実行 # VPCネットワークの作成 # サブネットのIPレンジは10.240.0.0/24(最大256node使用可能)にする gcloud compute networks create kubernetes-vpc-network --subnet-mode=custom gcloud compute networks subnets create kubernetes-vpc-network-subnet --range=10.240.0.0/24 --network=kubernetes-vpc-network # ネットワークのIPレンジ内からネットワーク上の任意のnodeへの任意のプロトコルでの接続を許可(kubernetes-vpc-network-allow-internal) # 任意の送信元からネットワーク上の任意のnodeへの特定ポートへの特定プロトコルでの接続を許可(kubernetes-vpc-network-allow-external) # tcp:22はnodeへのSSHに, tcp:6443はkube-apiserverへのリクエストに, icmpはpingでの監視に使用 gcloud compute firewall-rules create kubernetes-vpc-network-allow-internal --network=kubernetes-vpc-network --allow all --source-ranges=10.240.0.0/24 gcloud compute firewall-rules create kubernetes-vpc-network-allow-external --network kubernetes-vpc-network --allow tcp:22,tcp:6443,icmp VMインスタンスの作成 つぎにクラスタのnodeとして使うVMインスタンスをmaster用とworker用にそれぞれ1台ずつ建てる。\nCreating a cluster with kubeadmによるとmasterは最低でも2GiB RAM, 2CPUsがあるといいらしいのでマシンタイプはそれを満たすように気をつける。\nOSの起動imageはUbuntu 20.04 LTSを使用した。\n# Macで実行 # master(e2-medium),worker(e2-small)用インスタンスの作成 gcloud compute instances create kubernetes-master --machine-type=e2-medium --image-family=ubuntu-2004-lts --image-project=ubuntu-os-cloud --subnet=kubernetes-vpc-network-subnet gcloud compute instances create kubernetes-worker --machine-type=e2-small --image-family=ubuntu-2004-lts --image-project=ubuntu-os-cloud --subnet=kubernetes-vpc-network-subnet 最終的に以下のようになっていればOK。\n$ gcloud compute networks list --filter=\u0026#34;name=kubernetes-vpc-network\u0026#34; NAME SUBNET_MODE BGP_ROUTING_MODE IPV4_RANGE GATEWAY_IPV4 kubernetes-vpc-network CUSTOM REGIONAL $ gcloud compute networks subnets list --filter=\u0026#34;name=kubernetes-vpc-network-subnet\u0026#34; NAME REGION NETWORK RANGE STACK_TYPE IPV6_ACCESS_TYPE IPV6_CIDR_RANGE EXTERNAL_IPV6_CIDR_RANGE kubernetes-vpc-network-subnet us-central1 kubernetes-vpc-network 10.240.0.0/24 IPV4_ONLY $ gcloud compute firewall-rules list --filter=\u0026#34;network=kubernetes-vpc-network\u0026#34; NAME NETWORK DIRECTION PRIORITY ALLOW DENY DISABLED kubernetes-vpc-network-allow-external kubernetes-vpc-network INGRESS 1000 tcp:22,tcp:6443,icmp False kubernetes-vpc-network-allow-internal kubernetes-vpc-network INGRESS 1000 all False $ gcloud compute instances list NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS kubernetes-master us-central1-a e2-medium 10.240.0.4 xx.xx.xx.xxx RUNNING kubernetes-worker us-central1-a e2-small 10.240.0.5 yy.yyy.yyy.yy RUNNING nodeの準備 ネットワークとnode用VMが準備できたので、いよいよクラスタを構築していく。\n# SSHするときは以下のコマンドを使う gcloud compute ssh kubernetes-master gcloud compute ssh kubernetes-worker コンテナランタイム(containerd)のインストール masterとworkerで共にPod(コンテナ)を動かすために必要なコンテナランタイムをインストールする。\nコンテナランタイムにはいくつか候補があるが、今回はcontainerdを使う。\nUbuntuでのインストール手順はInstall Docker Engine on Ubuntuを参考にする。\n# master/worker両方でそれぞれ実行する # VM起動時にkernelモジュール(overlay, br_netfilter)を読み込むための設定 cat \u0026lt;\u0026lt;EOF | sudo tee /etc/modules-load.d/containerd.conf overlay br_netfilter EOF # kernelモジュール(overlay, br_netfilter)の手動読み込み sudo modprobe overlay \u0026amp;\u0026amp; sudo modprobe br_netfilter # kernelパラメータを変更(永続化)する設定 cat \u0026lt;\u0026lt;EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1 net.bridge.bridge-nf-call-ip6tables = 1 EOF # kernelパラメータの適用 sudo sysctl --system # (ないと思うが)古いpackageを削除 sudo apt-get remove docker docker-engine docker.io containerd runc # aptのpackage indexの更新とHTTPSでリポジトリを使うために必要なpackageのインストール sudo apt-get update \u0026amp;\u0026amp; sudo apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release # Docker公式のGPG鍵追加 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg # Dockerのstableリポジトリ追加 echo \u0026#34;deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\u0026#34; | sudo tee /etc/apt/sources.list.d/docker.list \u0026gt; /dev/null # containerdのインストール sudo apt-get update \u0026amp;\u0026amp; sudo apt-get install -y containerd.io # containerdの設定ファイル作成(96行目付近にsystemdのcgroupドライバーを使う設定を入れる) sudo mkdir -p /etc/containerd containerd config default | sudo tee /etc/containerd/config.toml sudo vim /etc/containerd/config.toml # containerdの再起動 sudo systemctl restart containerd containerdを動かすcgroupがsystemdになる/etc/containerd/config.tomlの設定はUsing the systemd cgroup driverに従った。\n[plugins.\u0026#34;io.containerd.grpc.v1.cri\u0026#34;.containerd.runtimes] [plugins.\u0026#34;io.containerd.grpc.v1.cri\u0026#34;.containerd.runtimes.runc] runtime_type = \u0026#34;io.containerd.runc.v2\u0026#34; runtime_engine = \u0026#34;\u0026#34; runtime_root = \u0026#34;\u0026#34; privileged_without_host_devices = false base_runtime_spec = \u0026#34;\u0026#34; [plugins.\u0026#34;io.containerd.grpc.v1.cri\u0026#34;.containerd.runtimes.runc.options] SystemdCgroup = true systemdでcontainerdが動いていることを確認できればOK。\nkubernetes-worker:~$ systemctl is-active containerd active kubelet,kubeadm,kubectlのインストール コンテナランタイムがインストールできたら、次はInstalling kubeadm, kubelet and kubectlに従ってKubernetesクラスタに必要なコンポーネントをmasterとworker両方にインストールする。\n# master/worker両方でそれぞれ実行する # Google CloudのGPG鍵追加 sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg # Kubernetesリポジトリ追加 echo \u0026#34;deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main\u0026#34; | sudo tee /etc/apt/sources.list.d/kubernetes.list # kubelet,kubeadm,kubectlのインストールとバージョン固定 sudo apt-get update \u0026amp;\u0026amp; sudo apt-get install -y kubelet kubeadm kubectl \u0026amp;\u0026amp; sudo apt-mark hold kubelet kubeadm kubectl masterの起動 コンテナランタイムとkubeadmの準備ができたらまずはmaster node (control-plane)を作成する。\n今回はネットワークアドオンにCalicoを使うため、kubeadm initのオプションを付与する。\n# masterでのみ実行する # master nodeのセットアップ sudo kubeadm init --pod-network-cidr=192.168.0.0/16 # kubeconfigの準備 mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config # kubectlの準備 source \u0026lt;(kubectl completion bash) alias k=kubectl complete -F __start_kubectl k これでmaster nodeが作成できた\u0026hellip;と思いきや、\nこの時点ではまだネットワークアドオンが入っていないためPod間の通信ができず、CoreDNSが立ち上がらない。\nkubernetes-master:~$ k get pods -A NAMESPACE NAME READY STATUS RESTARTS AGE kube-system coredns-78fcd69978-dzwrb 0/1 Pending 0 3m25s kube-system coredns-78fcd69978-ffpvj 0/1 Pending 0 3m25s kube-system etcd-kubernetes-master 1/1 Running 0 3m33s kube-system kube-apiserver-kubernetes-master 1/1 Running 0 3m33s kube-system kube-controller-manager-kubernetes-master 1/1 Running 0 3m33s kube-system kube-proxy-zk7mn 1/1 Running 0 3m25s kube-system kube-scheduler-kubernetes-master 1/1 Running 0 3m33s ネットワークアドオン(Calico)のインストール kubeadm initが終わったので、\nつぎにネットワークアドオンとしてCalicoをインストールする。\n# masterでのみ実行する # Calicoのインストール k create -f https://docs.projectcalico.org/manifests/tigera-operator.yaml k create -f https://docs.projectcalico.org/manifests/custom-resources.yaml Calicoに必要なPodの作成には時間がかかるのですこし待つ。\n次のようになっていればOK。\nCoreDNSも立ち上がっている。\nkubernetes-master:~$ k get pods -A NAMESPACE NAME READY STATUS RESTARTS AGE calico-apiserver calico-apiserver-6dd4bc68c6-lslcj 1/1 Running 0 12s calico-system calico-kube-controllers-767ddd5576-64dj8 1/1 Running 0 80s calico-system calico-node-2fx6b 1/1 Running 0 80s calico-system calico-typha-5bf9887dd7-hrtwj 1/1 Running 0 80s kube-system coredns-78fcd69978-dzwrb 1/1 Running 0 12m kube-system coredns-78fcd69978-ffpvj 1/1 Running 0 12m kube-system etcd-kubernetes-master 1/1 Running 0 12m kube-system kube-apiserver-kubernetes-master 1/1 Running 0 12m kube-system kube-controller-manager-kubernetes-master 1/1 Running 0 12m kube-system kube-proxy-zk7mn 1/1 Running 0 12m kube-system kube-scheduler-kubernetes-master 1/1 Running 0 12m tigera-operator tigera-operator-59f4845b57-qgq6c 1/1 Running 0 111s workerの起動 master1台のKubernetesクラスタができたので、\n次にworkerをこのクラスタに追加する。\nnodeの追加に必要なトークンとCA証明書のハッシュ値はmaster側で取得する。\n# masterでのみ実行する # 新規tokenの作成 kubernetes-master:~$ kubeadm token create \u0026lt;token\u0026gt; # CA証明書ハッシュの取得 kubernetes-master:~$ openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2\u0026gt;/dev/null | openssl dgst -sha256 -hex | sed \u0026#39;s/^.* //\u0026#39; \u0026lt;hash\u0026gt; # master nodeのホストとIPを取得 kubernetes-master:~$ k cluster-info | grep control Kubernetes control plane is running at https://\u0026lt;control-plane-host\u0026gt;:\u0026lt;control-plane-port\u0026gt; masterで取得した情報を使ってworkerをkubeadm joinしてクラスタに追加する。\n# workerでのみ実行する sudo kubeadm join --token \u0026lt;token\u0026gt; \u0026lt;control-plane-host\u0026gt;:\u0026lt;control-plane-port\u0026gt; --discovery-token-ca-cert-hash sha256:\u0026lt;hash\u0026gt; This node has joined the clusterと表示されればworkerがクラスタに追加できている。\nmaster側で確認するとworkerが認識されていて、各種DaemonSetのPodもちゃんとworkerに配置されている。\nkubernetes-master:~$ k get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME kubernetes-master Ready control-plane,master 25m v1.22.2 10.240.0.4 \u0026lt;none\u0026gt; Ubuntu 20.04.3 LTS 5.11.0-1020-gcp containerd://1.4.11 kubernetes-worker Ready \u0026lt;none\u0026gt; 91s v1.22.2 10.240.0.5 \u0026lt;none\u0026gt; Ubuntu 20.04.3 LTS 5.11.0-1020-gcp containerd://1.4.11 kubernetes-master:~$ k get all -A -o wide NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES calico-apiserver pod/calico-apiserver-6dd4bc68c6-lslcj 1/1 Running 0 19m 192.168.237.5 kubernetes-master \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; calico-system pod/calico-kube-controllers-767ddd5576-64dj8 1/1 Running 0 20m 192.168.237.2 kubernetes-master \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; calico-system pod/calico-node-2fx6b 1/1 Running 0 20m 10.240.0.4 kubernetes-master \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; calico-system pod/calico-node-vkmhc 1/1 Running 0 7m7s 10.240.0.5 kubernetes-worker \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; calico-system pod/calico-typha-5bf9887dd7-hrtwj 1/1 Running 0 20m 10.240.0.4 kubernetes-master \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; calico-system pod/calico-typha-5bf9887dd7-v8plr 1/1 Running 1 (6m33s ago) 7m4s 10.240.0.5 kubernetes-worker \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system pod/coredns-78fcd69978-dzwrb 1/1 Running 0 31m 192.168.237.1 kubernetes-master \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system pod/coredns-78fcd69978-ffpvj 1/1 Running 0 31m 192.168.237.3 kubernetes-master \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system pod/etcd-kubernetes-master 1/1 Running 0 31m 10.240.0.4 kubernetes-master \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system pod/kube-apiserver-kubernetes-master 1/1 Running 0 31m 10.240.0.4 kubernetes-master \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system pod/kube-controller-manager-kubernetes-master 1/1 Running 0 31m 10.240.0.4 kubernetes-master \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system pod/kube-proxy-xj444 1/1 Running 0 7m7s 10.240.0.5 kubernetes-worker \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system pod/kube-proxy-zk7mn 1/1 Running 0 31m 10.240.0.4 kubernetes-master \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kube-system pod/kube-scheduler-kubernetes-master 1/1 Running 0 31m 10.240.0.4 kubernetes-master \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; tigera-operator pod/tigera-operator-59f4845b57-qgq6c 1/1 Running 0 20m 10.240.0.4 kubernetes-master \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR calico-apiserver service/calico-api ClusterIP 10.110.66.91 \u0026lt;none\u0026gt; 443/TCP 19m apiserver=true calico-system service/calico-kube-controllers-metrics ClusterIP 10.106.106.192 \u0026lt;none\u0026gt; 9094/TCP 19m k8s-app=calico-kube-controllers calico-system service/calico-typha ClusterIP 10.110.34.28 \u0026lt;none\u0026gt; 5473/TCP 20m k8s-app=calico-typha default service/kubernetes ClusterIP 10.96.0.1 \u0026lt;none\u0026gt; 443/TCP 31m \u0026lt;none\u0026gt; kube-system service/kube-dns ClusterIP 10.96.0.10 \u0026lt;none\u0026gt; 53/UDP,53/TCP,9153/TCP 31m k8s-app=kube-dns NAMESPACE NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE CONTAINERS IMAGES SELECTOR calico-system daemonset.apps/calico-node 2 2 2 2 2 kubernetes.io/os=linux 20m calico-node docker.io/calico/node:v3.20.2 k8s-app=calico-node kube-system daemonset.apps/kube-proxy 2 2 2 2 2 kubernetes.io/os=linux 31m kube-proxy k8s.gcr.io/kube-proxy:v1.22.2 k8s-app=kube-proxy NAMESPACE NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR calico-apiserver deployment.apps/calico-apiserver 1/1 1 1 19m calico-apiserver docker.io/calico/apiserver:v3.20.2 apiserver=true calico-system deployment.apps/calico-kube-controllers 1/1 1 1 20m calico-kube-controllers docker.io/calico/kube-controllers:v3.20.2 k8s-app=calico-kube-controllers calico-system deployment.apps/calico-typha 2/2 2 2 20m calico-typha docker.io/calico/typha:v3.20.2 k8s-app=calico-typha kube-system deployment.apps/coredns 2/2 2 2 31m coredns k8s.gcr.io/coredns/coredns:v1.8.4 k8s-app=kube-dns tigera-operator deployment.apps/tigera-operator 1/1 1 1 20m tigera-operator quay.io/tigera/operator:v1.20.4 name=tigera-operator NAMESPACE NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR calico-apiserver replicaset.apps/calico-apiserver-6dd4bc68c6 1 1 1 19m calico-apiserver docker.io/calico/apiserver:v3.20.2 apiserver=true,pod-template-hash=6dd4bc68c6 calico-apiserver replicaset.apps/calico-apiserver-788fc95f55 0 0 0 19m calico-apiserver docker.io/calico/apiserver:v3.20.2 apiserver=true,pod-template-hash=788fc95f55 calico-system replicaset.apps/calico-kube-controllers-767ddd5576 1 1 1 20m calico-kube-controllers docker.io/calico/kube-controllers:v3.20.2 k8s-app=calico-kube-controllers,pod-template-hash=767ddd5576 calico-system replicaset.apps/calico-typha-5bf9887dd7 2 2 2 20m calico-typha docker.io/calico/typha:v3.20.2 k8s-app=calico-typha,pod-template-hash=5bf9887dd7 kube-system replicaset.apps/coredns-78fcd69978 2 2 2 31m coredns k8s.gcr.io/coredns/coredns:v1.8.4 k8s-app=kube-dns,pod-template-hash=78fcd69978 tigera-operator replicaset.apps/tigera-operator-59f4845b57 1 1 1 20m tigera-operator quay.io/tigera/operator:v1.20.4 name=tigera-operator,pod-template-hash=59f4845b57 これで1master-1workerのKubernetesクラスタをつくることができた。\n後はVMのリソースが尽きない限り何でも自由にやりたい放題できる。\nおかたづけ GCPで建てたVMは今回masterに使用したe2-mediumだけでも1ヶ月で約3000円くらいかかってしまう。\nお金に余裕があればいいんだけど、練習用のクラスタなので使わなくなったらさっさと片付けるべき。\n(昔デカめのKubernetesクラスタを放置してたら無料枠$300が1ヶ月で吹き飛んた)\nVMをそのまま吹き飛ばしても良いのだが、練習としてkubeadmを使ったお掃除をしてみる。\n# masterでのみ実行する # workerのPodを退去させる kubectl drain kubernetes-worker --delete-local-data --force --ignore-daemonsets # workerでのみ実行する # kubadm joinによって設定されたものを設定前に戻す sudo kubeadm reset sudo rm -rf /etc/cni/net.d # masterでのみ実行する # workerを削除する kubectl delete node kubernetes-worker # kubeadm initによって設定されたものを設定前に戻す sudo kubeadm reset sudo rm -rf /etc/cni/net.d あとは粛々とKubernetesコンポーネントとコンテナランタイムを削除していく。\n# master/worker両方でそれぞれ実行する # Kubernetesコンポーネントの削除 sudo apt-get purge -y --allow-change-held-packages kubelet kubeadm kubectl sudo apt-get purge -y --allow-change-held-packages containerd.io 最後にgcloudで作ったリソースを削除する。\n# Macで実行 # GCPリソースの削除(いきなりこれをやってもOK) gcloud compute instances delete kubernetes-master kubernetes-worker gcloud compute firewall-rules delete kubernetes-vpc-network-allow-internal kubernetes-vpc-network-allow-external gcloud compute networks subnets delete kubernetes-vpc-network-subnet gcloud compute networks delete kubernetes-vpc-network これで綺麗さっぱりおかたづけできたのでお財布のダメージも少ないはず。\nおわり Creating a cluster with kubeadmをなぞって練習用のKubernetesクラスタをつくった。\n基本的にはドキュメントさえ見ればできるんだけど、参照するドキュメントが何枚もあったりしてたまに混乱するので具体的な手順を1枚にできてよかった。\nおまけ ","date":"2021-10-15T00:00:00Z","image":"/post/2021-10-15-create-kubernetes-cluster-with-kubeadm-containerd-calico/sotochan.jpg","permalink":"/post/2021-10-15-create-kubernetes-cluster-with-kubeadm-containerd-calico/","title":"Ubuntu+kubeadm+containerd+CalicoでシングルmasterのKubernetesクラスタをつくる"},{"content":"まとめ CKSを受験した 今回も試験シミュレータ(Killer Shell)で十分対策できた 試験中に参照できるドキュメントはCKAよりも多い もくじ 試験対策 試験本番 結果 試験対策 CKAを取ってからモチベーションの高いうちにCKSを受けることにした。\n勉強から先に始めるといつまでたっても終わらないマンなので今回も試験日を先に決めて(CKS合格の約2週後)、それに向けて勉強する方式を取った。\nまずはCKSにバンドルされた教材(LFS260)を1週間で一通り読み込んだ。\n今回は初めて触る内容も多かったので割と真面目に読んで、Exerciseもちゃんと手を動かして解いた。\ntrivyやkube-benchなどのKubernetesにデフォルトで含まれていないツールについての練習もできてよかったと思う。\n量的にはCKA用の教材(LFS258)よりすこし少ない程度だったが、連休だったのもあってなんとか終わらせることができた。\n教材を終わらせた後、前回同様にKiller Shellの試験シミュレータで模擬試験を解いた。\nCKA合格が前提とされている試験だけあり、クラスタの設定に関する問題は普通に出てくるしさらに先述のツール類を使う問題も出るので内容としてはかなり難しかったように思う。\n初回はドキュメントを見ないとほぼ0点だった。\nまた、CKSではCKADやCKAと異なりKubernetes公式ドキュメント以外にもtrivy, sysdig, Falco, AppArmorのドキュメントが試験中に参照可能であることをここで初めて知った。\nこれらのツールも模擬試験では容赦なく出題されていたので、試験で使えそうなページをブックマークしておいた。\n試験本番 今回も自宅で試験を受けた。\nもちろんねこもいっしょ。\n今回は試験開始時刻になっても試験官が来ない?トラブルがあったため、5分遅れくらいでスタートした。\n試験前の試験官とのやりとりはこれまでと全く一緒。\n今回も試験時間は2時間で問題数は17問くらいだった。\nやはりKiller Shellの試験シミュレータでやった内容と似た問題が多く、今回も余裕を持って回答できた(つもり)。\nそれぞれの問題の難しさ的にはCKAと同じくらいかそれ以上だと感じたが、CKSで出る問題の場合はKubernetes以外のツールの使い方を問うようなものが多く、\nそういった汎用的な手順はだいたい公式ドキュメントに書いてあるのでそれを見れば解けてしまうようなものがいくつかあった。\nちなみに試験を受けたのが普段ねこにおやつをあげている時間だったので、今回そとちゃんがめちゃくちゃうるさかった。\n試験前にその日のぶんのおやつはあげていたのに\u0026hellip;😭\n結構ガッツリ鳴いていたが、試験官に怒られることはなかった。\n結果 結果は\u0026hellip;?\n合格だった。🎉\n合格点67点に対して75点とかなりギリギリの結果だった。\n今回は配点比率がかなり高めの問題で1つ致命的なミスをした自覚があるので、それが点数に響いたように思う。\nCKAD, CKAはそこそこの点数が取れていたのと比較すると、\nやはり実務であまり触れていないモノに対して試験勉強だけでなんとかしようとしたためにまだ本質を理解できていないことがよくわかった。\nおわり CKSに合格した。\nCKAD, CKAと併せてこれでKubernetesの認定資格(2021年10月時点で公開されているもの)をすべて取得できたので、\n†Kubernetes完全に理解した† と言って良いのでは？(アカン)\nとはいえCKSだけ点数が低かったあたり、やっぱり実務でさわんないとダメだなという気持ちになった。\n今後も慢心せずに頑張りたい。\nおまけ ","date":"2021-10-10T00:00:00Z","image":"/post/2021-10-10-cks/sotochan.jpg","permalink":"/post/2021-10-10-cks/","title":"CKSを受験した"},{"content":"まとめ 草 病院 草 約2年ぶりくらいに猫草を買った。\n草 pic.twitter.com/13K07YSLUy\n\u0026mdash; ずみし (@uzimihsr) September 20, 2021 またたびジャンキーのそとちゃんは猫草も大好きで食いつきがヤバい。\n昔あげたときにごはんそっちのけで食べては吐いてを繰り返し部屋中GEROまみれにした前科があるので、\n今回は猫草を部屋の外に出して、特定の時間だけ食べられるようにした。\n(部屋の中に置くとずっと食べ続けちゃう😭)\nなお\u0026hellip;😭\n草めっちゃ食べて吐いた pic.twitter.com/DgfwfqmsNF\n\u0026mdash; ずみし (@uzimihsr) September 29, 2021 (なんで吐いた後のねこっていつもどや顔してるの？)\n病院 そとちゃんのワクチンを打ちに病院に行った。\n病院いく pic.twitter.com/nxuDU9nnZY\n\u0026mdash; ずみし (@uzimihsr) September 22, 2021 そとちゃんは病院大好きねこ。\nお出かけするとわかったとたんキャリーバッグに自分から入る。\n病院についてからも余裕ぶっこき状態。\n待合室で騒ぐ子犬など眼中になく、バッグの中でごろごろして成猫の余裕を見せつける。\n(俺は犬が苦手なので怯えまくっていた)\n診察室に入ると今度は診察台の上で寝転んだり先生の机の上を探検してしまう。\nあまりの傍若無人ぶりに耐えかねた先生に「気に入ってくれてうれしいけど、ここはおうちじゃないのよ〜🏥」とやんわり(?)怒られる始末。😭\n注射も全然平気で、全く暴れず「にゃん」とも言われなかった。\nそれよりも体温測定(おしりの*に体温計を入れてはかる)が嫌だったらしい。\n体温計を入れるときだけは暴れて「に\u0026quot;ゃ\u0026quot;ん\u0026rdquo;💢」だった。\n(ちなみに体温は38.2℃だった。ねこって温かい)\n今回打ったのは3種混合ワクチンというやつ。\nそとちゃんはワクチンを最後に打ったのが2018年に保護されたときで、それ以降うちに来てからは打っていなかった。\n完全室内飼いかつ1匹だけなので今まで頭から抜けていたんだけど、それでも本当は年に1回の接種がおすすめらしい。\n#Vaccinated pic.twitter.com/t0WgPwBO0h\n\u0026mdash; ずみし (@uzimihsr) September 22, 2021 ワクチンを打ったついでにいろいろ診てもらった。\n先生によると首にちょっとだけしこりがあるのが気になったらしい。\n精密検査とかが必要なレベルではないらしいけど、毎日触ってみてもし肥大するようなら要検査らしいので注意したい。\nあと、右の奥歯が歯肉炎になっていて、何かが触れるだけで痛みを感じる状態らしい。😭\n歯みがきをやたら嫌がるのはこれが原因だった。\n左の奥歯は綺麗なので問題ないけど、歯周病菌がこれ以上増えないよう歯肉に塗る薬を処方してもらうことになった。\nインターベリーα\n犬用って書いてあるけど猫にも効果があるみたい。\nこれを週に2回×5週間、計10回塗る必要がある。\nまだ歯肉の痛みがあるそとちゃんはなかなか塗らせてくれないけど、よくなると信じてがんばるしかない。\n歯肉炎の薬塗られるの嫌すぎて立てこもり pic.twitter.com/Xoq49MCnH6\n\u0026mdash; ずみし (@uzimihsr) September 29, 2021 その他はだいたい健康で、体型も普通なので食事は今のままで問題ないとのことだった。\n(体重は4.05kgだった)\nワクチンを打った後は人間と同じく副反応が出ることがあるらしく、\n接種後は目を離さないように言われたのでその日はつきっきりで看ていた。\nが、特に何も変化はなくいつもどおりごろごろしていた。\nこのあたりは俺に似たのかもしれない。\nワクチン接種後2時間くらいは目を離さないよう言われてたけどなんもなかった\n元気 pic.twitter.com/ORRLgRletl\n\u0026mdash; ずみし (@uzimihsr) September 22, 2021 (ほとんど遊んでただけだが)そとちゃんは病院をがんばってえらかった。\n病院がんばった pic.twitter.com/PvxJYKrTGq\n\u0026mdash; ずみし (@uzimihsr) September 22, 2021 おわり 9月は病院に行ったのがかなり大きなイベントだった。\n最近はそとちゃんがいつも元気にしているのであまり病院に連れて行っていなかったけども、\n今回みたいにちょっと怪しい部分(特に歯)を診てもらえたり、何よりそとちゃんが楽しそうなので今後は定期的に連れていくようにしたい。\nおまけ ","date":"2021-09-30T00:00:00Z","image":"/post/2021-09-30-sotochan/sotochan.jpg","permalink":"/post/2021-09-30-sotochan/","title":"9月のそとちゃん(2021)"},{"content":"まとめ kubectl --asでService Accountを使いたいときはsystem:serviceaccount:(NAMESPACE):(SERVICEACCOUNT)を指定すると良い。\n# resourceに対するverbが許可されているかチェック kubectl auth can-i \u0026lt;verb\u0026gt; \u0026lt;resource\u0026gt; -n \u0026lt;namespace\u0026gt; --as system:serviceaccount:\u0026lt;namespace\u0026gt;:\u0026lt;serviceAccountName\u0026gt; # ServiceAccountに許可されたresource, verbをリストで表示 kubectl auth can-i --list -n \u0026lt;namespace\u0026gt; --as system:serviceaccount:\u0026lt;namespace\u0026gt;:\u0026lt;serviceAccountName\u0026gt; 環境 kind v0.11.0 go1.16.4 darwin/amd64 Kubernetes v1.21.1 もくじ 間違えていた 正しいやり方 間違えていた Service Accountにこんな感じの権限を設定していたとする。\n# namespace(myhome)のservice account(sotochan)がnamespace: myhomeのpodに対して何でもできる権限を付与 $ kubectl create namespace myhome $ kubectl create serviceaccount sotochan -n myhome $ kubectl create role pod-owner --verb=\u0026#34;*\u0026#34; --resource=\u0026#34;pods\u0026#34; -n myhome $ kubectl create rolebinding pod-owner-bind --role=pod-owner --serviceaccount=myhome:sotochan このとき、kubectl auth can-iでService Accountの権限を確認しようとしたらうまくいかなかった。\n# どうして... $ kubectl auth can-i get pod -n myhome --as sotochan no $ kubectl auth can-i --list -n myhome --as sotochan | grep -c pod 0 正しいやり方 公式ドキュメントをテキトーに探してもよくわからず\u0026quot;わからん！わからん！\u0026ldquo;って騒いでいたら会社の素晴らしい同期が教えてくれた。\n--asでService Accountを指定する場合はsystem:serviceaccount:(NAMESPACE):(SERVICEACCOUNT)と指定すればいいらしい。\n# やったぜ。 $ kubectl auth can-i get pod -n myhome --as system:serviceaccount:myhome:sotochan yes $ kubectl auth can-i --list -n myhome --as system:serviceaccount:myhome:sotochan | grep pod pods [] [] [*] 落ち着いて探したら公式ドキュメントにも一応関連する情報が書いてあった。\nService Accountのユーザー名がそもそもsystem:serviceaccount:(NAMESPACE):(SERVICEACCOUNT)として扱われているらしい。1\n知らなかった\u0026hellip;😭\nおわり Service Accountについてちゃんと理解してればこんなところで詰まらないとは思うけど、\n公式ドキュメントでkubectl --asでService Accountを指定する例が見つけづらかった+自分は30分くらい消耗してしまったので一応メモとして残しておく。\nかなしい\u0026hellip;\nおまけ https://kubernetes.io/docs/reference/access-authn-authz/authentication/#service-account-tokens\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2021-09-16T00:00:00Z","image":"/post/2021-09-16-kubectl-auth-can-i-service-account/sotochan.jpg","permalink":"/post/2021-09-16-kubectl-auth-can-i-service-account/","title":"kubectl auth can-iでService Accountの権限を確認する"},{"content":"まとめ CKAを受験した CKA-JPを受ける意味はないと思う 試験シミュレータの出来が良いのである程度慣れている人なら試験対策はこれだけでよさそう ねこちゃんは持ち込み可(重要) CKADを先に受けておいてよかった もくじ 受験まで 試験対策 試験本番 結果 受験まで CKADに合格してから約1年、Kubernetesにもだいぶ慣れてきたのでCKAを受験することにした。\n\u0026hellip;本当はだいぶ前に教材(LFS258)付きの受験コースを購入していて、\nそれを読み終わってから受験しようと思ってだらだらしてたらこの教材が中々のボリュームで時間がかかっていた。\n結局のところ、業務で経験を積んだおかげで教材の内容がほとんどわかる状態だったのと、\nいつまで経っても教材が終わらなかったので(主にソシャゲのせい)、\n先に試験日を決めてその前の数日で試験対策をして臨むことにした。\n試験の申し込みの流れはCKADを受験したときとほとんど同じだったので割愛。\n前回は日本語版で受けたが試験官とのやり取りが普通に英語だったりとあまりメリットを感じなかったので、CKA-JPではなく普通のCKAを受けた。\n何故かCKAのほうがちょっと安いし(2021年9月時点でCKAが$375, CKA-JPが$410)、やろうと思えば問題文も日本語にできるので普通にCKAを受けたほうが良いと思う。\n試験対策 試験対策はほとんど一夜漬け。\nLFS258のExerciseも良さそうだったけど、\nクラスタ構築のためにパブリッククラウド使うとお片付け忘れてお金無駄にしちゃうマンなので手は動かさずに内容をさらっと読む程度だった。\n前からあったのかもしれないが今はCKAに申し込むとKiller Shellとかいう試験シミュレータが無料で使えるので(36hx2回まで)、どちらかというとこっちのほうが役に立った。\n\u0026ldquo;本番の試験よりも難しいよ!(意訳)\u0026ldquo;との触れ込み通り、このシミュレータが結構難しい。\n練習問題25問+プレビュー問題3問が用意されているのだが、\nドキュメントを見ないと最初は練習問題が半分、プレビュー問題は1つくらいしか解けなかった。\nよくわからなかったところを回答と公式ドキュメントを見ながら再度解いた。\n特に自分はクラスタ構築をKubernetes The Hard Wayでしかやったことがなかった(普段はマネージドk8sを使っている)ので、\n主にkubeadmのコマンドまわりがわからず急いでドキュメントを読み込んだ。\n公式ドキュメントは本番試験中も参照可能なので、難しいと思った内容に関連するドキュメントはブックマークしておいた。\nPersistentVolume(Claim), NetworkPolicyあたりはkubectl create --dry-run -o yamlできないのでこれらのYAML例があるページも併せてブックマークした。\n試験本番 CKADと同様、自宅で試験を受けた。\n試験前の指示も前回と全く同じ(カメラを持って部屋を一周しろ、机を片付けろ、などなど)だったので割愛。\n前回そとちゃんを廊下で待たせたら試験中に爆鳴きして大変だったので、そとちゃんをカメラに映しながら試験官に\n\u0026ldquo;ウチにはねこちゃんいるんだけど、この子部屋の外に出さなきゃダメ?\u0026rdquo;\nと確認したところ\n\u0026ldquo;おとなしくできるなら彼女は部屋に居てもいいよ!\u0026rdquo;\nとのことだった。\n見た目だけでなんでそとちゃんが女の子(she)だってわかったのかは謎だけど(英語だと猫の代名詞でsheを使うことが多い?)、\nOKをもらえたのでよかった。\nまた、試験中に開けるタブは2つまで(試験用+ドキュメント用)とされているが、1タブずつで2ウィンドウ開いても良いとのことだった。\n自分の場合はディスプレイが横長なのでこれがかなり有利に働いたと思う。\n試験時間は2時間で問題数は17問くらい。\n具体的な内容は避けるが問題の傾向は試験シミュレータとかなり似ていて、\nたしかに本番のほうが少し簡単なものが多かった。\nまた、シミュレータと違って「n問目が正解できてないとm問目で詰む」というケースがなかったので、難しい問題は飛ばして後回しにできて良かった。\nCKAD(2時間で19問)のときは少し慎重になりすぎて時間が足りなかったので、\n今回はスピード重視でやったところ30分くらい時間が余って見直しする余裕もあった。\nちなみにそとちゃんは試験開始後に1回鳴いただけで、その後はずっとおとなしく寝てくれていた。えらい!\n結果 結果は\u0026hellip;?\n合格だった。🎉\n合格点66点に対して得点はまたも91点だった。\n今回は時間にも余裕があって、完答したつもりだったのですこしくやしい。\nおわり CKAに合格した。\n正直これを獲っただけで実用レベルのクラスタ管理者になれるかというとすこし疑問だけど、\n少なくともKubernetesの基本を押さえたことの証明にはなると思う。\n個人的にはCKADと比べると問題の内容はすこし難しいものの、制限時間のあるテストとして考えるとCKAのほうが問題数も少なくて簡単だった。\n(CKADを受けたおかげでkubectlの操作がやたら早くなっていたり、もちろん自分の経験値が増えていることもあるが\u0026hellip;)\nCKAD、CKAと来てしまったので次はCKSを受ける予定(いつ?)。\n噂によるとかなり難しいらしいので頑張りたい(ほんまか?)。\nおまけ ","date":"2021-09-14T00:00:00Z","image":"/post/2021-09-14-cka/sotochan.jpg","permalink":"/post/2021-09-14-cka/","title":"CKAはねこと一緒に受験できる"},{"content":"まとめ おでかけ ねずみ大捜索 ボロボロダンボール おでかけ そとちゃんがいつもより窓際で鳴いたりして外に出たがってるように見えたので、\nハーネス+抱っこの状態で何回か家の外に出してみた。\nセミを見てきた pic.twitter.com/Z5sqSvsCqd\n\u0026mdash; ずみし (@uzimihsr) August 25, 2021 元々野良猫だったそとちゃんだけど外に出ても意外とおとなしくできる。えらい。\nというよりはハーネスが嫌いすぎて動く気がないと言ったほうが正しい。\nつけられた時点で石のように動かなくなる(抱っこしやすくて助かる)。\n外に出てもあんまり遠くは見ずに、近くに落ちてる死にかけの虫をずっと睨んでいる。\nよくわからないけどこのときはしっぽをぶんぶん振るのでそとちゃん的にはめっちゃ楽しいらしい。\n(たまに反撃されてびっくりする)\n一応完全室内飼いなのでそとちゃんだけで外に出すことは絶対にしないけど、\nずっと室内だと退屈そうなのでたまにはこうやってお出かけしてもいいと思った。\nねずみ大捜索 実は6月くらいからねずみのおもちゃが全部行方不明になっていた。\n1ヶ月くらい失くしてたねずみが出てきた pic.twitter.com/cGhpu2NHxS\n\u0026mdash; ずみし (@uzimihsr) August 21, 2021 そとちゃんは遊んでるときにおもちゃが取れなくなると一応鳴いて教えてくれるんだけど、\nどこで失くしたかまでは教えてくれないのでその場で見つからなかったときはそのまま迷宮入りしてしまう。\nそんな感じでどんどんねずみが消えていって、ついに全部なくなっちゃったので気合をいれて大捜索。\n狭い部屋の中なのに1時間くらいかかってなんとか全部見つけられた。\n猫トイレの裏、洗面台の下、ベッドフレームとマットレスの隙間、冷蔵庫の裏側\u0026hellip;\nねこの手じゃ届かないのにどうやって入れた？ってところにも隠れててびっくりした。\n次は失くさずに上手に遊んでほしい。\n(この後すぐ白いのが1つ消えた😭)\nボロボロダンボール そとちゃんお気に入りのダンボールトンネル(元は自動給餌器の空き箱)。\n箱より袋派のそとちゃんだけどこれはほぼ毎日入る。\n(この中から飛び出して俺をビビらせるのがすき)\nそんなダンボールトンネルも2年使っているのでもうボロボロ。\nそとちゃんが入ったときに興奮して内側をバリバリしちゃうので、ついに大きな穴が空いてしまった。\nバリバリするたびにダンボールのカスが出るし、流石にもう捨てたいのだけど、なかなか許可が降りない\u0026hellip;😭\nおわり 8月のそとちゃんは元気いっぱいでよく遊んでいた。\n遊ぶ時間は毎日確保してるので運動不足ではないとおもうけど、この元気はどこからくるのだろうか\u0026hellip;\nおまけ ","date":"2021-09-09T00:00:00Z","image":"/post/2021-09-09-sotochan/sotochan.jpg","permalink":"/post/2021-09-09-sotochan/","title":"8月のそとちゃん(2021)"},{"content":"やったことのまとめ ログファイルをtailしてメトリクス化するmtailを使ってnginxのアクセスログをメトリクス化した https://github.com/uzimihsr/mtail-nginx つかうもの macOS Big Sur 11.2.3 Docker Desktop for Mac Version 3.5.2 Docker Engine Version 20.10.7 Docker Compose Version 1.29.2 mtail version 3.0.0-rc47 nginx 1.21.1 Prometheus v2.29.1 やったこと mtailのビルド mtail programの作成 動作確認 mtailのビルド mtail\bはアプリケーションのログをメトリクス化するツール。\n自身でメトリクスを公開できないアプリの監視なんかに使えそう。\nnginxのメトリクスを扱うexporterとしてはnginx-prometheus-exporterがあるんだけど、\nstub_statusの設定が必要だったりOSS版nginxだとメトリクスの種類が少なかったりするので今回はmtailを使ってみる。\nmtailのビルド方法はいくつかある1が、今回はかんたんに試したいのでGitHub Releasesで配布されているバイナリ2を仕込んだDockerイメージを作成する。\n(自前でビルドしようとしたら自分の環境ではテストのエラーが発生してうまくできなかった\u0026hellip;)\n# Docker imageのビルド $ ls Dockerfile Dockerfile $ docker image build -t mtail . $ docker container run -it --rm mtail --version mtail version 3.0.0-rc47 git revision 5e0099f843e4e4f2b7189c21019de18eb49181bf go version go1.16.5 go arch amd64 go os linux mtail programの作成 続いてログをメトリクスに変換するためのmtail programと呼ばれるスクリプトを作成する。\nmtail programはpattern(ログの各行に対する条件)とaction(patternを満たしたログに関する処理)から構成されている。3\n(awkにちょっと似ている)\npatternは主に正規表現で記述するので、まずはメトリクス化したいnginxのアクセスログのフォーマットを確認する。\n# nginxのログフォーマットを確認 $ docker container run -it --rm nginx:1.21.1 cat /etc/nginx/nginx.conf ...(省略) http { ... log_format main \u0026#39;$remote_addr - $remote_user [$time_local] \u0026#34;$request\u0026#34; \u0026#39; \u0026#39;$status $body_bytes_sent \u0026#34;$http_referer\u0026#34; \u0026#39; \u0026#39;\u0026#34;$http_user_agent\u0026#34; \u0026#34;$http_x_forwarded_for\u0026#34;\u0026#39;; ... } このときのログ\bの具体例は次のとおり。\n172.20.0.1 - - [18/Aug/2021:14:04:48 +0000] \u0026#34;GET / HTTP/1.1\u0026#34; 200 612 \u0026#34;-\u0026#34; \u0026#34;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36\u0026#34; \u0026#34;-\u0026#34; このフォーマットのログに対して正規表現で名前付きキャプチャグループを設定すると次のような感じになる。\n$requestはメソッド、URI、バージョンに分割するようにした。　^(?P\u0026lt;remote_addr\u0026gt;\\S+) - (?P\u0026lt;remote_user\u0026gt;.+) \\[(?P\u0026lt;time_local\u0026gt;.+)\\] \u0026#34;(?P\u0026lt;request_method\u0026gt;\\S+) (?P\u0026lt;request_uri\u0026gt;\\S+) (?P\u0026lt;request_version\u0026gt;\\S+)\u0026#34; (?P\u0026lt;status\u0026gt;\\S+) (?P\u0026lt;body_bytes_sent\u0026gt;\\S+) \u0026#34;(?P\u0026lt;http_referer\u0026gt;\\S+)\u0026#34; \u0026#34;(?P\u0026lt;http_user_agent\u0026gt;.+)\u0026#34; \u0026#34;(?P\u0026lt;http_x_forwarded_for\u0026gt;\\S+)\u0026#34;$ この正規表現をpatternとしたmtail programを次のように作成した。\nnginx_requestはリクエスト数を数えるカウンタで、ラベルとしてリクエストメソッドとステータスコードを付与するようにした。\nnginx_request_pattern_matching_failedはpatternにマッチしなかったログの行数を数えるカウンタとして使用する。\nまた、どちらもログファイル名としてsourceラベルを付与するようにした。\n動作確認 これでmtailを使う準備ができたのでDocker ComposeでnginxやPrometheusと一緒に起動してみる。\nmtailの実行時引数などは公式ドキュメント4を参考にした。　Docker版のnginxはアクセスログ(/var/log/nginx/access.log)とエラーログ(/var/log/nginx/error.log)がそれぞれ標準出力(/dev/stdout)と標準エラー出力(/dev/stderr)へのシンボリックリンクになっていて、\nファイルとして参照するのが難しかったのでログの出力先を変更するようにした。\n# ログファイルが標準出力へのシンボリックリンクになっている $ docker container run -it --rm nginx:1.21.1 ls -l /var/log/nginx total 0 lrwxrwxrwx 1 root root 11 Aug 17 11:46 access.log -\u0026gt; /dev/stdout lrwxrwxrwx 1 root root 11 Aug 17 11:46 error.log -\u0026gt; /dev/stderr # 起動前のディレクトリの状態 $ tree . ├── Dockerfile ├── README.md ├── docker-compose.yaml ├── mtail │ └── nginx.mtail ├── nginx │ └── nginx.conf └── prometheus └── prometheus.yml # 起動 $ docker compose up -d --force-recreate $ docker compose ps NAME COMMAND SERVICE STATUS PORTS mtail-nginx_mtail_1 \u0026#34;./mtail --progs=/et…\u0026#34; mtail running 0.0.0.0:3903-\u0026gt;3903/tcp, :::3903-\u0026gt;3903/tcp mtail-nginx_nginx_1 \u0026#34;/docker-entrypoint.…\u0026#34; nginx running 0.0.0.0:80-\u0026gt;80/tcp, :::80-\u0026gt;80/tcp mtail-nginx_prometheus_1 \u0026#34;/bin/prometheus --c…\u0026#34; prometheus running 0.0.0.0:9090-\u0026gt;9090/tcp, :::9090-\u0026gt;9090/tcp この状態でhttp://localhost:3903/を開くとmtailが正常に動作していることを確認できる。\n次にnginxhttp://localhost/に対していくつかリクエストを送ってみる。\n# nginxにホストOSからリクエストを送る $ curl -X GET \u0026#34;http://localhost/index.html\u0026#34; $ curl -X GET \u0026#34;http://localhost/index.html\u0026#34; $ curl -X GET \u0026#34;http://localhost/hogehoge.html\u0026#34; $ curl -X POST \u0026#34;http://localhost/index.html\u0026#34; $ curl -X PUT \u0026#34;http://localhost/index.html\u0026#34; $ curl -X DELETE \u0026#34;http://localhost/index.html\u0026#34; # ログファイルの内容 $ docker compose exec nginx cat /var/log/mtail-nginx/access.log 172.22.0.1 - - [19/Aug/2021:11:09:05 +0000] \u0026#34;GET /index.html HTTP/1.1\u0026#34; 200 612 \u0026#34;-\u0026#34; \u0026#34;curl/7.64.1\u0026#34; \u0026#34;-\u0026#34; 172.22.0.1 - - [19/Aug/2021:11:09:06 +0000] \u0026#34;GET /index.html HTTP/1.1\u0026#34; 200 612 \u0026#34;-\u0026#34; \u0026#34;curl/7.64.1\u0026#34; \u0026#34;-\u0026#34; 172.22.0.1 - - [19/Aug/2021:11:09:12 +0000] \u0026#34;GET /hogehoge.html HTTP/1.1\u0026#34; 404 153 \u0026#34;-\u0026#34; \u0026#34;curl/7.64.1\u0026#34; \u0026#34;-\u0026#34; 172.22.0.1 - - [19/Aug/2021:11:09:18 +0000] \u0026#34;POST /index.html HTTP/1.1\u0026#34; 405 157 \u0026#34;-\u0026#34; \u0026#34;curl/7.64.1\u0026#34; \u0026#34;-\u0026#34; 172.22.0.1 - - [19/Aug/2021:11:09:22 +0000] \u0026#34;PUT /index.html HTTP/1.1\u0026#34; 405 157 \u0026#34;-\u0026#34; \u0026#34;curl/7.64.1\u0026#34; \u0026#34;-\u0026#34; 172.22.0.1 - - [19/Aug/2021:11:09:27 +0000] \u0026#34;DELETE /index.html HTTP/1.1\u0026#34; 405 157 \u0026#34;-\u0026#34; \u0026#34;curl/7.64.1\u0026#34; \u0026#34;-\u0026#34; $ docker compose exec nginx cat /var/log/mtail-nginx/error.log 2021/08/19 11:09:12 [error] 31#31: *3 open() \u0026#34;/usr/share/nginx/html/hogehoge.html\u0026#34; failed (2: No such file or directory), client: 172.22.0.1, server: localhost, request: \u0026#34;GET /hogehoge.html HTTP/1.1\u0026#34;, host: \u0026#34;localhost\u0026#34; mtailのメトリクスはPrometheusのフォーマットに対応していて、\nhttp://localhost:3903/metricsで内容を確認できる。\nnginx.mtailで定義したとおり、\nアクセスログの内容は正規表現のpatternにマッチするのでメソッドとステータスがラベル化されてnginx_requestとしてカウントされていて、\nエラーログの内容はpatternにマッチしないのでnginx_request_pattern_matching_failedとしてカウントされている。\n最後に一応Prometheushttp://localhost:9090/graphでもメトリクスを確認する。\nmtailのメトリクスは通常のexporterと同様に扱えるので、時系列データとしてグラフ化もできている。\nやったぜ。🎉\nおわり mtailでnginxのログファイルをメトリクス化できた。\nnginxに限らず、ログのフォーマットが決まっていてそれにマッチする正規表現が書ければ何でもメトリクス化できるのでかなり便利だと思った。　おまけ https://github.com/google/mtail/blob/main/docs/Building.md\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/google/mtail/releases\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/google/mtail/blob/main/docs/Programming-Guide.md\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/google/mtail/blob/main/docs/Deploying.md\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2021-08-19T00:00:00Z","image":"/post/2021-08-19-mtail-nginx-access-log-regexp/sotochan.jpg","permalink":"/post/2021-08-19-mtail-nginx-access-log-regexp/","title":"mtailでnginxのaccess.logをメトリクス化する"},{"content":"はじめに(予防線) これはCOVID-19ワクチンモデルナ筋注を打った私個人の感想を書きなぐっただけの自己満日記です。\nCOVID-19やそれに関連する情報に対する個人の思想等は極力切り離して客観的に書いたつもりです。\n特定の人物または団体を攻撃したり擁護するような意図はなく、\n読者に同ワクチンの接種を強く勧めたり逆に止めさせようということもありません。\n仮にそう読めたとしてもそれは筆者の未熟な文章力によるものなのでお察しください。\nまとめ COVID-19ワクチンモデルナ筋注を打った 筋肉注射は痛くなかったり痛かったりした 1回目と2回目で別の腕に打っても問題ないらしい 副反応は軽めだった 1回目はほぼ何もなし 2回目は体調を崩したが接種翌々日以降は何もなし ある程度体調を崩すのに備えておいてよかった もくじ 打ったワクチン ワクチンを打った人(ぼく) 接種(1回目) 副反応?(1回目) 接種(2回目) 副反応?(2回目) 打ったワクチン COVID-19ワクチンモデルナ筋注1 約1ヶ月の間隔を空けて2回接種した ワクチンを打った人(ぼく) 年齢 20代後半 性別 男 身長 170cm程度 体重 60kg程度 その他健康面 特に大きな持病は無し 運動不足気味 食生活は乱れがち お酒はあまり飲まない タバコもあまり吸わない 接種(1回目) 予約前に厚生労働省のサイト2はちょっとだけ読んだ。\n化学はさっぱりなので成分とかはよくわからなかったけど、臨床試験の概要とか統計のとり方はおもしろかった。\n副反応があるという話は聞いていたので、それに備えてスポーツドリンクと頭に貼る冷却シートを多めに用意した。\n当日は午前10時~12時ごろに接種。\n健康状態は問題なく、普通に問診してそのままワクチンを打ってもらった。\n利き腕が右なので左肩に注射。\n結構ガッツリ肩を出して打ってもらうので、半袖Tシャツを着ていったのは正解だったと思う。\n筋肉注射がたぶん初めてなので緊張していたが、 担当の方が上手だったのか針が刺さっていることすら気づかず数秒で接種終了。\n当日は激しい運動をしない、打ったところは強く触らないように言われた。3\n接種後は会場内の椅子に座って15分待つよう指示があった。\n持病があったりしてリスクの高い人は30分待つように言われる場合もあるらしい。\n待機する場所には接種を終えた人(年齢性別バラバラ)がだいたい10~30人くらいいた。\n緊急時に備えて看護師?医師?の方が複数人準備してくれていたが、自分がお世話になることはなかったし、周りで具合が悪くなった人もいなかった。\n幸いなことに15分経っても何も起こらなかったのでそのまま退場。\n後述のとおり副反応?も軽かったので普通に仕事をして、\n昼ごはんは担々麺、晩ごはんはラーメンを食べて帰宅後はシャワーを少しだけ短めに浴びて普通に寝た。\n副反応?(1回目) 一応副反応というか普段と接種後で違う体の症状は次のようなものがあった。\n注射した左腕に筋肉痛に似た症状(接種から2時間後~24時間後) 腕を体の前方に向かって挙げる動き(フロントレイズ)はちょっと痛い 体の横から腕を挙げる動き(サイドレイズ)はめちゃくちゃ痛い 顎の下のリンパ節？のあたりに痛み(接種から24時間後~48時間後) 口を閉じていると特に気にならないが、大きく口をあけたり手で押すと痛い 下痢(接種当日夜) とはいえ、特に生活に支障をきたすほどの症状はなかった。\n翌日以降も顎下リンパ節?の痛み以外は何もなかったのでこれまで通りの生活をした。\n接種(2回目) 1回目とほぼ同じ時間帯に同じように接種。\n違う点があったとすれば、\n接種前日までにトラブルがあり左肩を強く打って内出血していたので、\n1回目とは異なり利き腕の右肩に注射を打ってもらった。\n問診のときに心配になって質問したけど、1回目と2回目で違う腕に打っても効果に問題はないらしい。\nまた、2回目の注射は針が刺さったのがわかるくらいの痛みがちょっとだけあった。\nこのあたりは打つ人のテクニックによる気がする。\n接種後の指示も1回目とは変わらず、同じように待機して同じように何もなかったので同じように退場。\n後述のだるさがあったので仕事も早めに切り上げた。\n昼ごはんはカレー、晩ごはんはラーメンを食べて軽くシャワーを浴びていつもより早めに寝た。\n副反応?(2回目) 前評判どおり2回目は1回目よりも体調の変化が大きかった。\n注射した右腕に筋肉痛に似た症状(接種から2時間後~12時間後) 1回目とだいたい同じだが、期間がちょっと短く接種当日の夜には治まった 全身の疲労感、関節の痛み、眠気(接種から4時間後~36時間後) 座ってるとなにもしたくなくなる、すぐ横になりたくなるくらいのだるさ 発熱(接種から24時間後~36時間後) 平熱+2℃くらい 寝ている間に熱でうなされて目が冷めた 下痢(接種当日~翌日夕方) 特に翌日朝からの発熱がつらかった。\n風邪のときほど苦しくはないけど、動くのがめんどくさかったり食欲が出なかったりで夕方まで寝て過ごした。\n1回目の接種前に用意した冷却シートがまるまる残っていたのと、保冷剤を首や脇に当てたおかげでいくらか楽だった。\nとにかく喉が渇いたので、スポーツドリンクを多めに用意しておいたのも正解だったと思う。\n俺が発熱してるのでねこが暖をとりにきた pic.twitter.com/FpddgoBsRF\n\u0026mdash; ずみし (@uzimihsr) July 27, 2021 2回目の副反応?はその後接種翌日の夕方(接種から36時間後)にはほとんどおさまり、\n以降は大きく体調を崩すこともなかった。\n(この記事は2回目の接種から2週後に書いているが、今現在体調で困っていることはない)\n周りで同じワクチンを接種した人の話を聞くともっと高い熱が2日以上出たケースもあったみたいで、\n自分はかなり軽く済んだ方だったらしい。\nよかった(?)。\nおわり COVID-19のワクチンについて周りの人の話やメディアを見ると「副反応が強い」というケースが多いように見えたので、\n副反応が結構軽め(?)だった人の話も少しはあると良いかと思って駄文を書いてみた。\n若者と呼ばれる世代の中では比較的早めに打ったほうだと思うので、人柱として誰かの参考になればいいと思う。\nおまけ https://www.take-care-covid-19.jp/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.mhlw.go.jp/stf/seisakunitsuite/bunya/vaccine_moderna.html\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.mhlw.go.jp/content/000805693.pdf\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2021-08-11T00:00:00Z","image":"/post/2021-08-11-covid-19-vaccinated/sotochan.jpg","permalink":"/post/2021-08-11-covid-19-vaccinated/","title":"COVID-19ワクチン(モデルナ筋注)を打った"},{"content":"まとめ かわいいポーズ パソコン妨害ねこ かわいいポーズ そとちゃんの得意技、おでこを床につけるポーズ\nはみがき断固拒否のポーズ pic.twitter.com/FfkCxGBvM2\n\u0026mdash; ずみし (@uzimihsr) July 1, 2021 甘えてるのか「かかってこいや」なのかはわからないけど、\n特に夕方のごはん前とか俺が外出して戻ってきたときによくやる。\nちなみにこのときおなかを触るとバチボコに噛んで掴んで蹴ってくる。\n椅子取られた pic.twitter.com/TqR1zFoYMx\n\u0026mdash; ずみし (@uzimihsr) July 7, 2021 かわいい。🥰\nパソコン妨害ねこ ちょっと前まで仕事はMacBookを普通に開いてやっていたんだけど、\n最近ついにディスプレイを買ってMacBookを閉じて使うようになった。\n机が広くなって快適！生産性向上！\nと思ったのだが、うちには妖怪パソコン妨害ねこちゃんがいるのを忘れていた。\n仕事バチクソ妨害ねこちゃんが現れた pic.twitter.com/nSR5Xde428\n\u0026mdash; ずみし (@uzimihsr) July 29, 2021 まずはシンプルに画面を観にくるパターン。\nこれがエスカレートすると次はディスプレイとキーボードの間に挟まってくる。\nこのとき躊躇なくキーを踏んだりするので普通に仕事に支障をきたす。\nまた、今までトラックパッドで作業していたのをマウスに変えたのだが、\nこれがそとちゃんの新たなおもちゃになってしまった。\nマウスは枕 pic.twitter.com/ftCYn2UfXZ\n\u0026mdash; ずみし (@uzimihsr) July 31, 2021 マウスの滑りが良いので、\n力の弱いそとちゃんでもかんたんに動かせるのが楽しいらしい。\nマウスを机から落としてご満悦 pic.twitter.com/hKeaNWWO3J\n\u0026mdash; ずみし (@uzimihsr) July 29, 2021 よかったね。😭\nおわり 7月のそとちゃんはよくつっかかってきた。\nこれまでに比べるとちょっと出社が多かったり(月に数回だが\u0026hellip;)して寂しい思いをさせているので、\nまたあまえんぼ期がきているかもしれない。\nおまけ ","date":"2021-08-08T00:00:00Z","image":"/post/2021-08-08-sotochan/sotochan.jpg","permalink":"/post/2021-08-08-sotochan/","title":"7月のそとちゃん(2021)"},{"content":"やったことのまとめ sample-controllerをDeploymentとしてkindのクラスタ内で動かした つかうもの macOS Big Sur 11.2.3 Docker Desktop for Mac Version 3.4.0 Docker Engine Version 20.10.7 Docker Compose Version 1.29.2 kind v0.11.0 go1.16.4 darwin/amd64 Kubernetes v1.21.1 やったこと コードの修正とimageのビルド kindでローカルimageを使う設定とDeploymentの作成 ClusterRoleとClusterRoleBindingの作成 再度動かしてみる コードの修正とimageのビルド 前回sample-controllerを動かしたときはコントローラー自体はクラスタ外で動かし,\nKubernetes APIへの接続にはローカルのkubeconfigを使用していた.\n今回はコントローラー自体もPodとしてクラスタ内で動かすため,\nKubernetes APIに接続するための情報をPodに持たせる必要がある.\nここで使えるのがclient-goのrest.InClusterConfig().\nPodにマウントされたServiceAccountのトークンを使ってKubernetes APIへ接続する設定を作ってくれる1.\nkubeconfigを使わないようにしたmain.go\nrest.InClusterConfig()を使うようにコードを書き換えた状態でDocker imageをビルドする.\nDockerfileはGo用のものをいい感じに書いてみた.\n# Docker imageのビルド $ ls Dockerfile Dockerfile $ docker image build -t sample-controller:develop . # Dockerでこのまま実行しても必要な情報が無いので怒られる $ docker container run sample-controller:develop panic: unable to load in-cluster configuration, KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT must be defined goroutine 1 [running]: main.main() /sample-controller/main.go:56 +0x685 とりあえず動きそう.\nkindでローカルimageを使う設定とDeploymentの作成 次にローカルでビルドしたカスタムコントローラーのDocker imageをkindで作ったクラスタで使えるように設定する2.\n# CRDが作成されていない場合は作成しておく $ kubectl apply -f artifacts/examples/crd.yaml customresourcedefinition.apiextensions.k8s.io/foos.samplecontroller.k8s.io created # ローカルDockerのimageをkindのクラスタで使えるようにする $ kind load docker-image sample-controller:develop Image: \u0026#34;sample-controller:develop\u0026#34; with ID \u0026#34;sha256:dde0eac9c3c272e80dc1e98c705c34ddf35b7d5c585fc32364563d2d96db386d\u0026#34; not yet present on node \u0026#34;kind-control-plane\u0026#34;, loading... このimageを使ったDeploymentを作成する.\n# Deploymentの作成 $ kubectl create deployment sample-controller --image=sample-controller:develop deployment.apps/sample-controller created $ kubectl get deployment NAME READY UP-TO-DATE AVAILABLE AGE sample-controller 1/1 1 1 2m11s $ kubectl get pods -l app=sample-controller NAME READY STATUS RESTARTS AGE sample-controller-f48d54cf4-drqwl 1/1 Running 0 2m31s # PodにデフォルトのServiceAccount(default:default)が設定されている # Tokenなどの情報が/var/run/secrets/kubernetes.io/serviceaccountにマウントされている(rest.InClusterConfig()はこれを参照している) $ kubectl get pod sample-controller-f48d54cf4-drqwl -o yaml apiVersion: v1 kind: Pod metadata: ... spec: containers: - image: sample-controller:develop ... name: sample-controller ... volumeMounts: - mountPath: /var/run/secrets/kubernetes.io/serviceaccount name: kube-api-access-vx85f readOnly: true ... serviceAccount: default serviceAccountName: default ... volumes: - name: kube-api-access-vx85f projected: defaultMode: 420 sources: - serviceAccountToken: expirationSeconds: 3607 path: token - configMap: items: - key: ca.crt path: ca.crt name: kube-root-ca.crt - downwardAPI: items: - fieldRef: apiVersion: v1 fieldPath: metadata.namespace path: namespace すると今度はDockerで動かしたときと異なり,\nServiceAccountの情報が取得できるのでコントローラーが動いた!\n\u0026hellip;が, 次はPodに自動で紐づくデフォルトのServiceAccount(default:default)にこのコントローラーで操作するDeploymentとFoo(カスタムリソース)の一覧を取得する権限(list)がないためエラーになってしまった.\n# 今度はconfigが作成できて動作を開始している # ...が, DeploymentとFoo(カスタムリソース)を参照する権限がないためうまく動いていない $ kubectl logs sample-controller-f48d54cf4-drqwl I0707 09:01:00.813715 1 controller.go:115] Setting up event handlers I0707 09:01:00.813806 1 controller.go:156] Starting Foo controller I0707 09:01:00.813810 1 controller.go:159] Waiting for informer caches to sync E0707 09:01:00.826284 1 reflector.go:138] pkg/mod/k8s.io/client-go@v0.0.0-20210701054555-843bb800b12a/tools/cache/reflector.go:167: Failed to watch *v1alpha1.Foo: failed to list *v1alpha1.Foo: foos.samplecontroller.k8s.io is forbidden: User \u0026#34;system:serviceaccount:default:default\u0026#34; cannot list resource \u0026#34;foos\u0026#34; in API group \u0026#34;samplecontroller.k8s.io\u0026#34; at the cluster scope E0707 09:01:00.826385 1 reflector.go:138] pkg/mod/k8s.io/client-go@v0.0.0-20210701054555-843bb800b12a/tools/cache/reflector.go:167: Failed to watch *v1.Deployment: failed to list *v1.Deployment: deployments.apps is forbidden: User \u0026#34;system:serviceaccount:default:default\u0026#34; cannot list resource \u0026#34;deployments\u0026#34; in API group \u0026#34;apps\u0026#34; at the cluster scope ...(以下繰り返し) # いったん消しておく $ kubectl delete deployment sample-controller deployment.apps \u0026#34;sample-controller\u0026#34; deleted ClusterRoleとClusterRoleBindingの作成 というわけで次はServiceAccountでDeployemntとFooのリソースを操作するための権限(ClusterRole)を作成する.\n今回は使用するsample-controllerが全namespaceを対象に操作を行うため3,\nnamespaceレベルの権限のRoleではなくクラスタレベルのClusterRoleを使用した.\n# ClusterRoleの作成 # DeploymentとFoo(カスタムリソース)とEventに対する全ての操作を許可する権限 $ kubectl create clusterrole foo-control --verb=\u0026#34;*\u0026#34; --resource=foo,deployment,event clusterrole.rbac.authorization.k8s.io/foo-control created $ kubectl get clusterrole foo-control -o yaml apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: creationTimestamp: \u0026#34;2021-07-12T14:04:28Z\u0026#34; name: foo-control resourceVersion: \u0026#34;4126888\u0026#34; uid: 9b628c44-0320-48eb-8861-0e3c5d8f9ddf rules: - apiGroups: - \u0026#34;\u0026#34; resources: - events verbs: - \u0026#39;*\u0026#39; - apiGroups: - apps resources: - deployments verbs: - \u0026#39;*\u0026#39; - apiGroups: - samplecontroller.k8s.io resources: - foos verbs: - \u0026#39;*\u0026#39; 最後に, 作成したClusterRoleをServiceAccountに紐付けるためのClusterRoleBindingを作成する.\n# ClusterRoleBindingの作成 # ClusterRole(foo-control)をServiceAccount(default:default)に紐付ける $ kubectl create clusterrolebinding foo-control-binding --clusterrole=foo-control --serviceaccount=default:default clusterrolebinding.rbac.authorization.k8s.io/foo-control-binding created $ kubectl get clusterrolebinding foo-control-binding -o yaml apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: creationTimestamp: \u0026#34;2021-07-12T14:06:04Z\u0026#34; name: foo-control-binding resourceVersion: \u0026#34;4127049\u0026#34; uid: 09bde8fd-752a-433b-845a-5584aa1324a8 roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: foo-control subjects: - kind: ServiceAccount name: default namespace: default これでPodにデフォルトで紐づくServiceAccount(default:default)にクラスタ内のDeploymentとFoo(カスタムリソース)を操作する権限が与えられた.\n再度動かしてみる 以上でコントローラーをDeploymentとして動かすために必要な準備ができたはずなので,\nもう一度挑戦してみる.\n# Deploymentの作成 $ kubectl create deployment sample-controller --image=sample-controller:develop deployment.apps/sample-controller created $ kubectl get deployment NAME READY UP-TO-DATE AVAILABLE AGE sample-controller 1/1 1 1 8s $ kubectl get pods -l app=sample-controller NAME READY STATUS RESTARTS AGE sample-controller-f48d54cf4-jgv4w 1/1 Running 0 12s # 動いた...! $ kubectl logs sample-controller-f48d54cf4-jgv4w I0712 14:07:14.403608 1 controller.go:115] Setting up event handlers I0712 14:07:14.404169 1 controller.go:156] Starting Foo controller I0712 14:07:14.404319 1 controller.go:159] Waiting for informer caches to sync I0712 14:07:14.705485 1 controller.go:164] Starting workers I0712 14:07:14.706297 1 controller.go:170] Started workers 動いた\u0026hellip;!\nPodのログの内容も前回と同じで, 問題なく動いているように見える.\n最後にFooリソースを作成してみる.\n# Fooを作成 $ cat \u0026lt;\u0026lt; EOF | kubectl apply -f - apiVersion: samplecontroller.k8s.io/v1alpha1 kind: Foo metadata: name: my-foo spec: deploymentName: my-foo replicas: 10 EOF foo.samplecontroller.k8s.io/my-foo created # Fooとそれによって管理されるDeploymentが作成されている $ kubectl get foo NAME AGE my-foo 40s $ kubectl get deployment NAME READY UP-TO-DATE AVAILABLE AGE my-foo 10/10 10 10 27s sample-controller 1/1 1 1 5m15s $ kubectl get pods --show-labels NAME READY STATUS RESTARTS AGE LABELS my-foo-65f55f8578-2j2g6 1/1 Running 0 47s app=nginx,controller=my-foo,pod-template-hash=65f55f8578 my-foo-65f55f8578-44tbj 1/1 Running 0 47s app=nginx,controller=my-foo,pod-template-hash=65f55f8578 my-foo-65f55f8578-5n58m 1/1 Running 0 47s app=nginx,controller=my-foo,pod-template-hash=65f55f8578 my-foo-65f55f8578-bdtwp 1/1 Running 0 47s app=nginx,controller=my-foo,pod-template-hash=65f55f8578 my-foo-65f55f8578-gq7hh 1/1 Running 0 47s app=nginx,controller=my-foo,pod-template-hash=65f55f8578 my-foo-65f55f8578-jb258 1/1 Running 0 47s app=nginx,controller=my-foo,pod-template-hash=65f55f8578 my-foo-65f55f8578-kr6mt 1/1 Running 0 47s app=nginx,controller=my-foo,pod-template-hash=65f55f8578 my-foo-65f55f8578-mmtlk 1/1 Running 0 47s app=nginx,controller=my-foo,pod-template-hash=65f55f8578 my-foo-65f55f8578-pk847 1/1 Running 0 47s app=nginx,controller=my-foo,pod-template-hash=65f55f8578 my-foo-65f55f8578-vpvf6 1/1 Running 0 47s app=nginx,controller=my-foo,pod-template-hash=65f55f8578 sample-controller-f48d54cf4-jgv4w 1/1 Running 0 5m35s app=sample-controller,pod-template-hash=f48d54cf4 # Fooで管理されているのでDeploymentを手で消してもすぐ復活する $ kubectl delete deployment my-foo deployment.apps \u0026#34;my-foo\u0026#34; deleted $ kubectl get deployment NAME READY UP-TO-DATE AVAILABLE AGE my-foo 0/10 10 0 17s sample-controller 1/1 1 1 6m58s # Statusも正常に更新されている $ kubectl get foo my-foo -o yaml apiVersion: samplecontroller.k8s.io/v1alpha1 kind: Foo metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {\u0026#34;apiVersion\u0026#34;:\u0026#34;samplecontroller.k8s.io/v1alpha1\u0026#34;,\u0026#34;kind\u0026#34;:\u0026#34;Foo\u0026#34;,\u0026#34;metadata\u0026#34;:{\u0026#34;annotations\u0026#34;:{},\u0026#34;name\u0026#34;:\u0026#34;my-foo\u0026#34;,\u0026#34;namespace\u0026#34;:\u0026#34;default\u0026#34;},\u0026#34;spec\u0026#34;:{\u0026#34;deploymentName\u0026#34;:\u0026#34;my-foo\u0026#34;,\u0026#34;replicas\u0026#34;:10}} creationTimestamp: \u0026#34;2021-07-12T14:11:58Z\u0026#34; generation: 23 name: my-foo namespace: default resourceVersion: \u0026#34;4128510\u0026#34; uid: a38bc894-769c-4dd0-9d2b-8b3be3ec864b spec: deploymentName: my-foo replicas: 10 status: availableReplicas: 10 # ログを見る限り正常にEventも吐かれている $ kubectl logs -f sample-controller-f48d54cf4-jgv4w I0712 14:07:14.403608 1 controller.go:115] Setting up event handlers I0712 14:07:14.404169 1 controller.go:156] Starting Foo controller I0712 14:07:14.404319 1 controller.go:159] Waiting for informer caches to sync I0712 14:07:14.705485 1 controller.go:164] Starting workers I0712 14:07:14.706297 1 controller.go:170] Started workers I0712 14:11:58.920953 1 controller.go:228] Successfully synced \u0026#39;default/my-foo\u0026#39; I0712 14:11:58.948805 1 event.go:291] \u0026#34;Event occurred\u0026#34; object=\u0026#34;default/my-foo\u0026#34; kind=\u0026#34;Foo\u0026#34; apiVersion=\u0026#34;samplecontroller.k8s.io/v1alpha1\u0026#34; type=\u0026#34;Normal\u0026#34; reason=\u0026#34;Synced\u0026#34; message=\u0026#34;Foo synced successfully\u0026#34; ...(以下繰り返し) Deploymentとして動かしたコントローラーでもちゃんとカスタムリソース(Foo)の管理ができた.\nやったぜ.\nおわり sample-controllerをDocker image化して, Deploymentとして動かすことができた.\n途中権限まわりでちょっとつまづいたけどなんとか乗り切れたので謎の達成感がある. うれしい.\n次は自分でカスタムコントローラーを書いてみたいけど, だいぶめんどくさい\u0026hellip;\nおまけ https://github.com/kubernetes/client-go/tree/master/examples/in-cluster-client-configuration\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://kind.sigs.k8s.io/docs/user/quick-start/#loading-an-image-into-your-cluster\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/kubernetes/sample-controller/blob/b8d9e8c247129e53962d0dcfc08a4e8b47477318/pkg/generated/informers/externalversions/factory.go#L91-L108\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2021-07-13T00:00:00Z","image":"/post/2021-07-13-kubernetes-run-sample-controller-as-deployment/sotochan.jpg","permalink":"/post/2021-07-13-kubernetes-run-sample-controller-as-deployment/","title":"Kubernetesのsample-controllerをDeploymentとして動かす"},{"content":"やったことのまとめ sample-controllerを読んで動かしてCRD(CustomResourceDefinition)とCustom Controllerの概要をつかんだ つかうもの macOS Big Sur 11.2.3 Docker Desktop for Mac Version 3.4.0 Docker Engine Version 20.10.7 Docker Compose Version 1.29.2 kind v0.11.0 go1.16.4 darwin/amd64 Kubernetes v1.21.1 やったこと CRDの作成 Controllerのデプロイ CRDの作成 Kubernetesには既存のPodやDeploymentなどのリソースに加えて独自のリソースを定義できるCustomResourceDefinition(CRD)1というリソースがある.\nこれを使うことで既存のリソースでは物足りない機能なんかを自分で作ることができるらしい. すごい.\nCRDを自分で書くのは骨が折れるので, 今回は公式のsample-controllerに付属のCRDを使ってみる.\nhttps://github.com/kubernetes/sample-controller/blob/master/artifacts/examples/crd.yaml\n動かす前に重要そうな設定項目(ほんとは全部重要だが\u0026hellip;)を確認しておく.\nkey value apiVersion \u0026ldquo;apiextensions.k8s.io/v1\u0026quot;で固定 kind \u0026ldquo;CustomResourceDefinition\u0026quot;で固定 metadata.name \u0026ldquo;{spec.names.plural}.{spec.group}\u0026ldquo;とする spec.group KubernetesのREST APIで使用するgroup spec.versions[].name KubernetesのREST APIで使用するAPIのversion spec.versions[].schema CustomResourceの構造の定義(たぶん一番重要) spec.scope CustomResourceをNamespace単位で管理する場合:\u0026ldquo;Namespaced\u0026rdquo;,\nCluster単位で管理する場合:\u0026ldquo;Cluster\u0026rdquo; spec.names Kubernetes APIやkubectlで扱うときの名前の定義 これを実際にクラスタに適用する.\n# CRDを適用 $ pwd /path/to/sample-controller $ kubectl apply -f artifacts/examples/crd.yaml customresourcedefinition.apiextensions.k8s.io/foos.samplecontroller.k8s.io created $ kubectl get crd NAME CREATED AT foos.samplecontroller.k8s.io 2021-06-28T13:49:53Z # CRDで定義したリソース(Foo)がAPIで操作可能になる $ kubectl api-resources | grep foo foos samplecontroller.k8s.io/v1alpha1 true Foo クラスタにCRD(foos.samplecontroller.k8s.io)が追加された.\n次にこのCRDに従ったリソースを作成する.\nhttps://github.com/kubernetes/sample-controller/blob/master/artifacts/examples/example-foo.yaml\n# CustomResourceを作成 $ kubectl apply -f artifacts/examples/example-foo.yaml foo.samplecontroller.k8s.io/example-foo created # CRDで定義した名前(Foo)で参照できる $ kubectl get foo NAME AGE example-foo 105s $ kubectl get foo example-foo -o yaml apiVersion: samplecontroller.k8s.io/v1alpha1 kind: Foo metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {\u0026#34;apiVersion\u0026#34;:\u0026#34;samplecontroller.k8s.io/v1alpha1\u0026#34;,\u0026#34;kind\u0026#34;:\u0026#34;Foo\u0026#34;,\u0026#34;metadata\u0026#34;:{\u0026#34;annotations\u0026#34;:{},\u0026#34;name\u0026#34;:\u0026#34;example-foo\u0026#34;,\u0026#34;namespace\u0026#34;:\u0026#34;default\u0026#34;},\u0026#34;spec\u0026#34;:{\u0026#34;deploymentName\u0026#34;:\u0026#34;example-foo\u0026#34;,\u0026#34;replicas\u0026#34;:1}} creationTimestamp: \u0026#34;2021-06-28T13:59:25Z\u0026#34; generation: 1 name: example-foo namespace: default resourceVersion: \u0026#34;2385464\u0026#34; uid: 560ced47-d085-4b92-b92c-1be78639f878 spec: deploymentName: example-foo replicas: 1 $ kubectl delete foo example-foo foo.samplecontroller.k8s.io \u0026#34;example-foo\u0026#34; deleted apiVersion, kindがCRDで独自に定義した内容に合致しているので,\nそれに沿ったFooというリソースを作成することができた.\nただし, この時点ではただ構造化されたデータがREST APIで扱えるようになっただけで,\nこのリソースがどういった振る舞いをするかなどの情報はどこにも定義されていない.\nこれについては次のカスタムコントローラーで定義していく.\nControllerのデプロイ カスタムコントローラーではリソースのstatusがspecで定義した所望の状態に近づくように対象のオブジェクトについて操作を繰り返す.\nこれを自分で書くのは大変なので, こちらも公式のサンプルをそのまま使う.\nhttps://github.com/kubernetes/sample-controller/blob/master/controller.go\n実際に動かす前にこのコントローラーを構成するコンポーネントについて確認しておく.\nInformer Kubernetes API(正確にはAPIをwatchしたReflectorによってオブジェクトが追加されたキュー)から変化のあったオブジェクトを順番に取り出す 取り出したオブジェクトをIndexerに渡す オブジェクトの状態に応じたEvent Handlerを呼び出す Indexer Informerから受け取ったオブジェクトをメタ情報(namespace/オブジェクト名)で参照可能な状態にしてスレッドセーフな領域に保持する 必要なKeyが与えられた場合は対象のオブジェクトを返す Resource Event Handlers Informerで取り出したオブジェクトの状態に応じた処理を行う 基本的には対象のオブジェクトのKeyをWork queueに追加する Work queue 処理が必要なオブジェクトのKeyを保持するキュー Process Item Work queueから取り出したKeyを参照してIndexerからオブジェクトを取り出し必要な操作を行う (画像は公式2のもの)\nこの中で実際のCRD(Foo)の挙動を決めているのはProcess Itemの部分で,\n今回使用するコントローラーの場合はsyncHandler()3がそれにあたる.\nsyncHandler()により, FooというCRDは\nSpecにDeployment名とレプリカ数の情報を, Statusに利用可能なレプリカ数の情報を持つ(ここまではCRDで定義) nginxコンテナ1台のPodをSpecで定義されたレプリカ数ぶん保持するDeploymentを管理する(syncHandler()で定義) Deploymentがなければ作り, Deploymentが保持するPod数(レプリカ数)がSpecで定義されたレプリカ数を満たすようDeploymentを更新する Deploymentの状態によってStatusを更新する(syncHandler()で定義) という振る舞いをするリソースとなる.\n実際にカスタムコントローラーを動かしてみる.\n# カスタムコントローラーのビルド $ go build -o sample-controller . # 有効なkubeconfigを指定して起動 $ ./sample-controller -kubeconfig=$HOME/.kube/config I0704 00:35:30.758496 96780 controller.go:115] Setting up event handlers I0704 00:35:30.758719 96780 controller.go:156] Starting Foo controller I0704 00:35:30.758731 96780 controller.go:159] Waiting for informer caches to sync I0704 00:35:30.858942 96780 controller.go:164] Starting workers I0704 00:35:30.858973 96780 controller.go:170] Started workers # (別のターミナルで実行)Fooリソースの作成 $ kubectl apply -f artifacts/examples/example-foo.yaml # カスタムコントローラー適用前とは異なりstatusに変化が生じている $ kubectl get foo example-foo -o yaml apiVersion: samplecontroller.k8s.io/v1alpha1 kind: Foo metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {\u0026#34;apiVersion\u0026#34;:\u0026#34;samplecontroller.k8s.io/v1alpha1\u0026#34;,\u0026#34;kind\u0026#34;:\u0026#34;Foo\u0026#34;,\u0026#34;metadata\u0026#34;:{\u0026#34;annotations\u0026#34;:{},\u0026#34;name\u0026#34;:\u0026#34;example-foo\u0026#34;,\u0026#34;namespace\u0026#34;:\u0026#34;default\u0026#34;},\u0026#34;spec\u0026#34;:{\u0026#34;deploymentName\u0026#34;:\u0026#34;example-foo\u0026#34;,\u0026#34;replicas\u0026#34;:1}} creationTimestamp: \u0026#34;2021-07-03T15:37:32Z\u0026#34; generation: 3 name: example-foo namespace: default resourceVersion: \u0026#34;3127576\u0026#34; uid: 5bbab9a1-0baf-41b1-8f68-93be619a6048 spec: deploymentName: example-foo replicas: 1 status: availableReplicas: 1 # Fooで定義されたDeploymentが作成されている $ kubectl get deployment NAME READY UP-TO-DATE AVAILABLE AGE example-foo 1/1 1 1 96s # Deploymentを削除してもカスタムコントローラーによりFooのspecを満たすようにすぐ再作成される $ kubectl delete deployment example-foo deployment.apps \u0026#34;example-foo\u0026#34; deleted $ kubectl get deployment NAME READY UP-TO-DATE AVAILABLE AGE example-foo 1/1 1 1 5s # FooリソースのSpecをいじっても問題なく作成できる $ cat \u0026lt;\u0026lt; EOF | kubectl apply -f - apiVersion: samplecontroller.k8s.io/v1alpha1 kind: Foo metadata: name: my-foo spec: deploymentName: my-foo replicas: 10 EOF $ kubectl get foo my-foo NAME AGE my-foo 23s $ kubectl get deployment my-foo NAME READY UP-TO-DATE AVAILABLE AGE my-foo 10/10 10 10 36s CRD(Foo)で定義したリソースに対して所望の処理が行われることが確認できた.\nここで面白いのはこのコントローラーがクラスタ外で稼働しているという点.\n各種リソースの操作はKubernetes APIを通じて行われるので,\nAPIに接続さえできればコントローラーの実体はどこで動いていても関係ないらしい.\n(デフォルトのkube-controller-managerと同様にDeploymentとして動かしても問題ないはず)\nおわり CRDとカスタムコントローラーのサンプルで遊んでみた.\n実際にコードを読んだりしてカスタムリソースとかコントローラーの仕組みがわかった気がする.\n時間があれば今回使ったカスタムコントローラーをクラスタ内でDeploymentとして動かしてみたい.\nおまけ https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/kubernetes/sample-controller/blob/master/docs/controller-client-go.md\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/kubernetes/sample-controller/blob/db75202208d77b1af79a3b04ee7612fb82564c6a/controller.go#L240-L319\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2021-07-12T00:00:00Z","image":"/post/2021-07-12-kubernetes-crd-controller-practice/sotochan.jpg","permalink":"/post/2021-07-12-kubernetes-crd-controller-practice/","title":"Kubernetesのsample-controllerで遊ぶ"},{"content":"まとめ イス おもちゃ 目ヤニがひどい イス そとちゃんが以前にも増して仕事用のイスに座るようになった.\n仕事だめです pic.twitter.com/bINUc9pAab\n\u0026mdash; ずみし (@uzimihsr) June 3, 2021 前はたまに座っている程度だったのが,\n今ではほぼ毎朝, というか俺が座っていないときは基本的にそとちゃんが座っている.\nおかげで仕事を始めるのがいつも遅くなる\u0026hellip;\nもういる pic.twitter.com/qqDOhstuVR\n\u0026mdash; ずみし (@uzimihsr) June 9, 2021 おもちゃで気を引くと降りてくれるんだけど,\n休憩とかで一時的に離席するとその間にまた座っちゃう.\n昼休憩 pic.twitter.com/aYl0sp6zzq\n\u0026mdash; ずみし (@uzimihsr) June 22, 2021 イスの座り心地が気に入っているのと,\nどうも\u0026quot;ここに座るとかまってもらえる\u0026quot;と覚えてしまったように思う.\nかしこい.\nまたいる pic.twitter.com/7EBNE8qOkC\n\u0026mdash; ずみし (@uzimihsr) June 10, 2021 でもたまに俺が座ってるときに\u0026quot;どけ!\u0026ldquo;って言ってる気もする.\nお昼寝終わっていきなりにゃんにゃん騒ぐから何事かと思ったら椅子を譲れということだった pic.twitter.com/b0jBCViXhH\n\u0026mdash; ずみし (@uzimihsr) May 31, 2021 なんでこんなに座りたがるのかはよくわからないが座っているときはとりあえず機嫌が良いので助かる.\nあとかわいい.😊\nご満悦 pic.twitter.com/ciAjNIslWf\n\u0026mdash; ずみし (@uzimihsr) June 28, 2021 おもちゃ またおもちゃをいくつか買ってしまった.\nまずはねこじゃらし.\n本物のエノコログサではなく人工のやつだけど, 割と喰い付きが良い.\nじゃれ猫　猫のお遊び草　２本セット\n緑と茶色の2本入りで, そとちゃん的には緑色のほうが好きみたい.\nこれ\u0026quot;は\u0026quot;買ってよかった.\n次はこのニジマス.\nニジマス pic.twitter.com/voQLvW0Sr8\n\u0026mdash; ずみし (@uzimihsr) June 13, 2021 じゃれぐるみ\n中にマタタビの枝と葉っぱがパンパンに詰まっているヤバいブツとの評判に期待して購入.\n評判通り, 俺の鼻でもわかるくらいにマタタビの匂いがプンプンする.\nまたたびジャンキーのそとちゃんは大興奮!\n\u0026hellip;だったのは初日だけ.\n初めこそ本能むき出しでベロンベロンに舐めていたものの,\n表面のマタタビが薄くなってからはたまに匂いを嗅ぐくらいになった.\nあとこのニジマスの重さが200gくらいあって,\nあんまり力が強くないそとちゃんには咥えて歩いたりするのも難しかったみたい.\nちなみに今までまたたびをあげたときも匂いを嗅ぐよりベロベロ舐めて舐めてトリップすることが多かった気がする.\n鼻から吸うより経口摂取のほうが\u0026quot;効く\u0026quot;んだろうか\u0026hellip;?🤔\n最後は猫ちゃんテンション爆アゲＢＯＸ.\n猫ちゃんテンション爆アゲBOXを買いました pic.twitter.com/xFrXfryxH1\n\u0026mdash; ずみし (@uzimihsr) June 24, 2021 開ける前からわかる圧倒的出オチ感.\n完成前からテンション爆アゲ pic.twitter.com/nnjzh6LRES\n\u0026mdash; ずみし (@uzimihsr) June 24, 2021 \u0026hellip;結局組み立て中がピークで,\n完成直後にちょっと遊んだだけで二度とテンションが爆アゲすることはなかった.\nはい.😭\n目ヤニがひどい ちょっと暖かくなって湿度も上がるとそとちゃんの右目の目ヤニがひどくなってくる.\nそとちゃんは子猫のころの猫風邪の後遺症で右目の涙腺が詰まっていて,\n涙がそのまま目から溢れてしまって目ヤニができやすい.\n因果関係はよくわからないけど,\n寒くて乾燥していた時期に比べるとその症状がひどくなるように思う.\nお昼寝の後とかはちょっと痛々しいくらいに茶色い目やにが固まってしまうのがかわいそう.\nこまめに拭いてはいるのだが\u0026hellip;😭\nおわり 梅雨の時期は部屋がじめじめするし,\n外の景色もどんよりしていてそとちゃんもあまり元気がなさそう.\nそとちゃんがだらだらごろごろしてるのもかわいいけど,\nこのままだと運動不足になっちゃうのではやく梅雨明けしてほしい.\nおまけ ","date":"2021-07-08T00:00:00Z","image":"/post/2021-07-08-sotochan/sotochan.jpg","permalink":"/post/2021-07-08-sotochan/","title":"6月のそとちゃんまとめ(2021)"},{"content":"まとめ KubernetesのPodとJobにそれぞれ稼働時間の上限activeDeadlineSecondsが指定できるけど, ちょっと挙動が違うので調べてみた.\n種類 activeDeadlineSeconds超過時の挙動 Pod kubeletによりコンテナがKILL(停止)されるKubernetes API上にはphase:FailedのPodが残るnode上には停止したコンテナが残るPodとコンテナはガベージコレクションのタイミングで削除される Job Job Controllerによりphase:ActiveのPodが明示的に削除される\n(それ以外のphaseは残る)Kubernetes API上にはJobが残り, Podは削除されるPodが削除されるため, node上のコンテナも削除される 環境 macOS Big Sur 11.2.3 Docker Desktop for Mac Version 3.4.0 Docker Engine Version 20.10.7 Docker Compose Version 1.29.2 kind v0.11.0 go1.16.4 darwin/amd64 Kubernetes v1.21.1 もくじ PodのactiveDeadlineSeconds JobのactiveDeadlineSeconds 詳しく見てみる PodのactiveDeadlineSeconds Podに設定できるactiveDeadlineSecondsの説明は次の通り.\n$ kubectl explain pod.spec.activeDeadlineSeconds KIND: Pod VERSION: v1 FIELD: activeDeadlineSeconds \u0026lt;integer\u0026gt; DESCRIPTION: Optional duration in seconds the pod may be active on the node relative to StartTime before the system will actively try to mark it failed and kill associated containers. Value must be a positive integer. (意訳) Podが開始してからactiveDeadlineSecondsより長く稼働してるとシステムがPodを失敗扱いにしてコンテナをKILLするよ\nということで適当なPodを作成して確認してみる.\n# Pod作成 $ kubectl apply -f pod-01.yaml # Podが開始した時刻(startTime)の20秒後に削除されている(containerStatuses[].state.terminated.finishedAt) # プロセスのexitCodeが137(シグナルを受けて終了)となっているので, # おそらくkubeletからコンテナランタイム(containerd)経由でSIGTERMが送られている. $ kubectl get pod -o yaml pod-01 | yq -C r - \u0026#34;status\u0026#34; containerStatuses: - containerID: containerd://f32f5d2995838397faaf0d2c4f312d941c1047001cb85e968fb4685cc75b5bda image: docker.io/library/busybox:latest imageID: docker.io/library/busybox@sha256:930490f97e5b921535c153e0e7110d251134cc4b72bbb8133c6a5065cc68580d lastState: {} name: main-container ready: false restartCount: 0 state: terminated: containerID: containerd://f32f5d2995838397faaf0d2c4f312d941c1047001cb85e968fb4685cc75b5bda exitCode: 137 finishedAt: \u0026#34;2021-06-11T13:32:19Z\u0026#34; reason: Error startedAt: \u0026#34;2021-06-11T13:31:31Z\u0026#34; message: Pod was active on the node longer than the specified deadline phase: Failed podIP: 10.244.0.36 podIPs: - ip: 10.244.0.36 qosClass: Guaranteed reason: DeadlineExceeded startTime: \u0026#34;2021-06-11T13:31:29Z\u0026#34; # ログ(途中まで)は残っている $ kubectl logs pod-01 --timestamps 2021-06-11T13:31:41.951165516Z 1 2021-06-11T13:31:51.933749153Z 2 2021-06-11T13:32:01.934770631Z 3 2021-06-11T13:32:11.936129723Z 4 起動した時刻(startTime)からactiveDeadlineSecondsで設定した秒数以上経過したあと(containerStatuses[].state.terminated.finishedAt)にPodがphase:Failedになっている.\n(たぶん同期のタイミングとかで少し遅れている)\n試しにnode上のコンテナの状態も確認してみる.\n# kindなのでnodeもdockerコンテナとして動いている $ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 05f0d805c4dc kindest/node:v1.21.1 \u0026#34;/usr/local/bin/entr…\u0026#34; 25 hours ago Up 25 hours 127.0.0.1:53211-\u0026gt;6443/tcp kind-control-plane # nodeの中のコンテナをcontainerStatuses[].containerIDで探してみると確かにコンテナが残っている(プロセスは終了している) $ docker container exec 05f0d805c4dc ctr --namespace k8s.io container ls | grep f32f5d2995838397faaf0d2c4f312d941c1047001cb85e968fb4685cc75b5bda f32f5d2995838397faaf0d2c4f312d941c1047001cb85e968fb4685cc75b5bda docker.io/library/busybox:latest io.containerd.runc.v2 以上より,\nPodの稼働時間がactiveDeadlineSecondsを超えてしまった場合はPodの情報がKubernetes APIに残り,\nnodeにもコンテナの情報が残ることがわかった(プロセスはシグナルを受けて異常終了する).\nJobのactiveDeadlineSeconds JobにもactiveDeadlineSecondsを設定できる. 説明は以下の通り.\n# JobのactiveDeadlineSecondsの説明 $ kubectl explain job.spec.activeDeadlineSeconds KIND: Job VERSION: batch/v1 FIELD: activeDeadlineSeconds \u0026lt;integer\u0026gt; DESCRIPTION: Specifies the duration in seconds relative to the startTime that the job may be continuously active before the system tries to terminate it; value must be positive integer. If a Job is suspended (at creation or through an update), this timer will effectively be stopped and reset when the Job is resumed again. (意訳) Jobが開始してからactiveDeadlineSecondsより長く稼働してるとシステムがJobを消すよ, アップデート(nodeの更新?)等でJobが中断した場合は再度稼働時間を数え直してくれるよ\nPodのときと同様にJobを作成して動作を確認してみる.\nコンテナの中身はだいたいおなじ.\n# Job作成 $ kubectl apply -f job-01.yaml # DeadlineExceededによりJobがFailedになっている $ kubectl get job -o yaml job-01 | yq -C r - \u0026#34;status\u0026#34; conditions: - lastProbeTime: \u0026#34;2021-06-11T14:02:17Z\u0026#34; lastTransitionTime: \u0026#34;2021-06-11T14:02:17Z\u0026#34; message: Job was active longer than specified deadline reason: DeadlineExceeded status: \u0026#34;True\u0026#34; type: Failed failed: 1 startTime: \u0026#34;2021-06-11T14:01:57Z\u0026#34; # Podが見つからない... $ kubectl get pods -l job-name=job-01 No resources found in default namespace. $ kubectl get pods No resources found in default namespace. # Eventを確認するとPod(job-01-xntdw)が明示的に削除されている $ kubectl describe job job-01 ...(省略) Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal SuccessfulCreate 7m44s job-controller Created pod: job-01-xntdw Normal SuccessfulDelete 7m24s job-controller Deleted pod: job-01-xntdw Warning DeadlineExceeded 7m24s job-controller Job was active longer than specified deadline # 念の為nodeをimage名で探しても見つからない... # (kindなのでnodeもdockerコンテナとして動いている) $ docker container exec 05f0d805c4dc ctr --namespace k8s.io container ls | grep -c busybox 0 今度はJobは残っているもののPodが消えてしまった\u0026hellip;\nJobの稼働時間がactiveDeadlineSecondsを超えた場合はPodがKubernetes APIから削除され,\nnode上のコンテナも削除されてしまうらしい.\nちなみにPodとJob両方にactiveDeadlineSecondsを設定した場合はどうなるか?\nまずはPodのactiveDeadlineSeconds \u0026lt; JobのactiveDeadlineSecondsの条件で試してみる.\n# Job作成 $ kubectl apply -f job-02.yaml # 今度はDeadlineExceededでなくBackoffLimitExceededによりJobがFailedになっている $ kubectl get job -o yaml job-02 | yq -C r - \u0026#34;status\u0026#34; conditions: - lastProbeTime: \u0026#34;2021-06-11T14:30:25Z\u0026#34; lastTransitionTime: \u0026#34;2021-06-11T14:30:25Z\u0026#34; message: Job has reached the specified backoff limit reason: BackoffLimitExceeded status: \u0026#34;True\u0026#34; type: Failed failed: 1 startTime: \u0026#34;2021-06-11T14:30:05Z\u0026#34; # Podが残っている $ kubectl get pods -l job-name=job-02 NAME READY STATUS RESTARTS AGE job-02-d2w6v 0/1 Error 0 2m16s # PodのactiveDeadlineSeconds超過時の挙動をしている $ kubectl get pod -o yaml job-02-d2w6v | yq -C r - \u0026#34;status\u0026#34; containerStatuses: - containerID: containerd://607cdbd019d8a1dc97473f6e62960ec68c0e70575b9c8dc7ac1fdfa18dab1dbd image: docker.io/library/busybox:latest imageID: docker.io/library/busybox@sha256:930490f97e5b921535c153e0e7110d251134cc4b72bbb8133c6a5065cc68580d lastState: {} name: main-container ready: false restartCount: 0 state: terminated: containerID: containerd://607cdbd019d8a1dc97473f6e62960ec68c0e70575b9c8dc7ac1fdfa18dab1dbd exitCode: 137 finishedAt: \u0026#34;2021-06-11T14:30:55Z\u0026#34; reason: Error startedAt: \u0026#34;2021-06-11T14:30:08Z\u0026#34; message: Pod was active on the node longer than the specified deadline phase: Failed podIP: 10.244.0.39 podIPs: - ip: 10.244.0.39 qosClass: Guaranteed reason: DeadlineExceeded startTime: \u0026#34;2021-06-11T14:30:05Z\u0026#34; # ログが残っている $ kubectl logs job-02-d2w6v --timestamps 2021-06-11T14:30:18.567995219Z 1 2021-06-11T14:30:28.568766279Z 2 2021-06-11T14:30:38.570408867Z 3 2021-06-11T14:30:48.548898562Z 4 # nodeにコンテナも残っている # (kindなのでnodeもdockerコンテナとして動いている) $ docker container exec 05f0d805c4dc ctr --namespace k8s.io container ls | grep 607cdbd019d8a1dc97473f6e62960ec68c0e70575b9c8dc7ac1fdfa18dab1dbd 607cdbd019d8a1dc97473f6e62960ec68c0e70575b9c8dc7ac1fdfa18dab1dbd docker.io/library/busybox:latest io.containerd.runc.v2 おそらくPodのactiveDeadlineSecondsのほうが短いため, JobのactiveDeadlineSecondsを超える前にPodがphase:Failedになったように見える.\n次にPodのactiveDeadlineSeconds \u0026gt; JobのactiveDeadlineSecondsの条件で試してみる.\n# Job作成 $ kubectl apply -f job-03.yaml # 今度はDeadlineExceededでJobがFailedになっている $ kubectl get job -o yaml job-03 | yq -C r - \u0026#34;status\u0026#34; conditions: - lastProbeTime: \u0026#34;2021-06-11T14:47:00Z\u0026#34; lastTransitionTime: \u0026#34;2021-06-11T14:47:00Z\u0026#34; message: Job was active longer than specified deadline reason: DeadlineExceeded status: \u0026#34;True\u0026#34; type: Failed failed: 1 startTime: \u0026#34;2021-06-11T14:46:40Z\u0026#34; # Podが残っていない $ kubectl get pods -l job-name=job-03 No resources found in default namespace. # Podが明示的に削除されている $ kubectl describe job job-03 ...(省略) Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal SuccessfulCreate 83s job-controller Created pod: job-03-h66hc Normal SuccessfulDelete 63s job-controller Deleted pod: job-03-h66hc Warning DeadlineExceeded 63s job-controller Job was active longer than specified deadline # nodeを探してもコンテナが見つからない # (kindなのでnodeもdockerコンテナとして動いている) $ docker container exec 05f0d805c4dc ctr --namespace k8s.io container ls | grep -c busybox 0 今度はJobのactiveDeadlineSecondsのほうが短いため, その時点でphase:ActiveだったPodとコンテナが削除されてしまった.\n詳しく見てみる これらの挙動の違いはおそらく各オブジェクトを管理しているコンポーネントの違いから来ている.\nPod Podはkubeletによって管理されていて,\nactiveDeadlineSecondsを超過している場合はactiveDeadlineHandler.ShouldEvict()1とKubelet.generateAPIPodStatus()2によってKubernetes API上のPodがphase:Failedにされ, Kubelet.KillPod()3によってコンテナがKILLされるみたい.\nまた, 公式Docsにも次のように書かれているあたりkubeletがKubernetes API上からPod情報を削除するような処理はほぼないと思われる.\n失敗したPodは人間またはcontrollerが明示的に削除するまで存在します。4\n(詳しく調べたところkubeletがKubernetes API上のPod情報を消してそうな部分が一つだけ見つかった5が, これはStatic Podを扱う場合にしか呼び出されないため, 通常のPodには関係ないはず.)\nこのため, PodがactiveDeadlineSecondsを超過したときの挙動は通常のPodが異常終了してFailedとなる場合と変わらず,\nやがてはガベージコレクションで削除される.\nactiveDeadlineSecondsを超過してもPodとコンテナが残っていたのはこのためだと思う.\nJob じゃあJobはどうなのかというと, こちらはkubeletではなくJob Controllerによって管理されている.\nJobがactiveDeadlineSecondsを超過した場合はController.syncJob()6とRealPodControl.DeletePod()7によってKubernetes APIを呼び出してその時点でPhase: ActiveなPodを明示的に削除するみたい.\nしたがって, backoffLimitの設定によりJobのPodが失敗しても再度作成されるような場合は最後の1つだけがAPI上から削除される.\n# Job作成 $ kubectl apply -f job-04.yaml # 今度はDeadlineExceededによりJobがFailedになっている $ kubectl get job -o yaml job-04 | yq -C r - \u0026#34;status\u0026#34; conditions: - lastProbeTime: \u0026#34;2021-06-12T00:35:57Z\u0026#34; lastTransitionTime: \u0026#34;2021-06-12T00:35:57Z\u0026#34; message: Job was active longer than specified deadline reason: DeadlineExceeded status: \u0026#34;True\u0026#34; type: Failed failed: 1 startTime: \u0026#34;2021-06-12T00:35:07Z\u0026#34; # Podが1つ(自身のactiveDeadlineSecondsによりphase:Failedとなったもの)だけ残っている $ kubectl get pods -l job-name=job-04 NAME READY STATUS RESTARTS AGE job-04-vsw8x 0/1 Error 0 2m43s # JobのactiveDeadlineSeconds超過時にActiveだったPod(job-04-5bjnz)が明示的に削除されている $ kubectl describe job job-04 ...(省略) Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal SuccessfulCreate 2m59s job-controller Created pod: job-04-vsw8x Normal SuccessfulCreate 2m19s job-controller Created pod: job-04-5bjnz Normal SuccessfulDelete 2m9s job-controller Deleted pod: job-04-5bjnz Warning DeadlineExceeded 2m9s (x2 over 2m9s) job-controller Job was active longer than specified deadline # nodeを探してもコンテナは1つだけ # (kindなのでnodeもdockerコンテナとして動いている) $ docker container exec 05f0d805c4dc ctr --namespace k8s.io container ls | grep -c busybox 1 おわり PodとJobのactiveDeadlineSecondsの違いを確認した.\n個人的にはPodが消えちゃうと後から処理に時間がかかった理由がわからなくなっちゃうので,\n単純なJobの場合はPodのactiveDeadlineSecondsを設定するほうが良い気がする.\nコードを読むのは疲れる\u0026hellip;\nおまけ https://github.com/kubernetes/kubernetes/blob/v1.21.1/pkg/kubelet/active_deadline.go#L63-L98\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/kubernetes/kubernetes/blob/v1.21.1/pkg/kubelet/kubelet_pods.go#L1526-L1530\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/kubernetes/kubernetes/blob/v1.21.1/pkg/kubelet/kubelet_pods.go#L846-L865\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://kubernetes.io/ja/docs/concepts/workloads/pods/pod-lifecycle/#pod-garbage-collection\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/kubernetes/kubernetes/blob/v1.21.1/pkg/kubelet/pod/mirror_client.go#L109-L137\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/kubernetes/kubernetes/blob/v1.21.1/pkg/controller/job/job_controller.go#L526-L541\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/kubernetes/kubernetes/blob/v1.21.1/pkg/controller/controller_utils.go#L597-L614\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2021-06-12T00:00:00Z","image":"/post/2021-06-12-kubernetes-active-deadline-seconds/sotochan.jpg","permalink":"/post/2021-06-12-kubernetes-active-deadline-seconds/","title":"KubernetesのactiveDeadlineSecondsはJobとPodでちょっと違う"},{"content":"やったことのまとめ クライアント認証がかかったPrometheusにGrafanaでアクセスした.\nPrometheusへの認証に必要な情報(証明書と秘密鍵)をGrafanaに持たせることができるので,\nGrafanaへアクセスする権限さえあればクライアント認証のかかったPrometheusのメトリクスが閲覧できるようになる.\nつかうもの macOS Big Sur 11.2.3 Docker Desktop for Mac Version 3.3.1 Docker Engine Version 20.10.5 Docker Compose Version 1.29.0 Prometheus (Docker) v2.27.1 nginx (Docker) 1.21.0 Grafana (Docker) 7.5.7 やったこと クライアント認証をかけたPrometheusの準備 Grafanaでクライアント認証を突破する クライアント認証をかけたPrometheusの準備 Prometheusにクライアント認証をかけるで用意したものを使う.\n秘密鍵や証明書もそのまま同じもの.\n(ただしGrafanaにはクライアント認証をかけないのでMacに証明書を読み込ませる必要はなし)\n今回はnginxを挟んで使う方法を採用するが, Prometheus単独でクライアント認証をかけた場合でもGrafanaの設定は同じはず.\nGrafanaでクライアント認証を突破する GrafanaはDataSource(Prometheus)への認証方法としてクライアント認証に対応しているので,\nクライアント証明書とクライアント秘密鍵(クライアントの身元を証明するのに使用),\nサーバー認証局の証明書(サーバーの身元を確認するのに使用)を持たせる.\nGrafanaがDataSourceへクライアント認証で接続するための設定はdatasource.yaml1に記述する.\n証明書と秘密鍵をベタ書きしたくないので, File provider2を使ってファイル名を指定して指定して読み込むようにした.\n(このあたりはissue3を参考にした)\n各種ファイルをGrafanaのコンテナにマウントして起動する.\n(今回はProemtheus+nginxの構成にしているが, Prometheus単独でクライアント認証をかけた場合でもGrafana側の設定は同じになる)\n# ディレクトリの状態(使わないものを消している) $ ls -1 client-ca-cert.pem client-cert.pem client-private-key.pem datasource.yaml docker-compose.yml https.conf server-cert.pem server-private-key.pem # コンテナを起動する $ docker compose up -d --force-recreate --remove-orphans 今回Grafanaは単独で動かしている(HTTPS化もしていない)のでChromeでhttp://localhost:3000を開く.\n(GrafanaはデフォルトでBasic認証がかかっているので, admin:adminで突破する)\nGrafanaでnginxのクライアント認証を突破し,\nPrometheusのメトリクスを取得できることが確認できた.\n# コンテナを終了しておく $ docker compose down おわり Grafanaでクライアント認証のかかったPrometheusからメトリクスを習得する流れを確認した.\nPrometheusのセキュリティまわりはこれで一安心な感じもするが, 要件によってはGrafanaにも別の認証をかけたほうがいいかも\u0026hellip;\nおまけ https://grafana.com/docs/grafana/latest/administration/provisioning/#data-sources\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://grafana.com/docs/grafana/latest/administration/configuration/#file-provider\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/grafana/grafana/issues/25945\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2021-06-07T00:00:00Z","image":"/post/2021-06-07-grafana-prometheus-client-auth/sotochan.jpg","permalink":"/post/2021-06-07-grafana-prometheus-client-auth/","title":"クライアント認証のかかったPrometheusにGrafanaでアクセスする"},{"content":"やったことのまとめ Prometheusにクライアント認証をかけてみた.\nPrometheus単独でクライアント認証を行う方法と,\nクライアント認証をかけたnginxでリバースプロキシする方法をそれぞれ試した.\nPrometheus, nginxはすべてDocker Desktop for Macで動かした.\nつかうもの macOS Big Sur 11.2.3 Docker Desktop for Mac Version 3.3.1 Docker Engine Version 20.10.5 Docker Compose Version 1.29.0 Prometheus (Docker) v2.27.1 nginx (Docker) 1.21.0 OpenSSL 1.1.1d やったこと 各種秘密鍵と証明書の作成 Prometheus単独でクライアント認証をかける Prometheus+nginxでクライアント認証をかける 各種秘密鍵と証明書の作成 まずはクライアント認証に必要なサーバーとクライアントの秘密鍵+証明書をOpenSSLで作成する.\n# nginxコンテナを起動してOpenSSLを使う $ docker container run --rm -it -v=\u0026#34;$PWD:/workdir\u0026#34; -w=\u0026#34;/workdir\u0026#34; --entrypoint=/bin/bash nginx:1.21.0 # サーバー用のオレオレ証明書を作成 # ホスト名はprometheus.hogehoge.comとしてSANsの設定もしておく(Chromeで開くため) $ openssl genpkey -algorithm RSA -out server-private-key.pem $ openssl req -x509 -key server-private-key.pem -out server-cert.pem -addext \u0026#39;subjectAltName = DNS:prometheus.hogehoge.com\u0026#39; Country Name (2 letter code) [AU]: State or Province Name (full name) [Some-State]: Locality Name (eg, city) []: Organization Name (eg, company) [Internet Widgits Pty Ltd]: Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []:prometheus.hogehoge.com Email Address []: # クライアント認証局の証明書を作成(オレオレ認証局) $ openssl genpkey -algorithm RSA -out client-ca-private-key.pem $ openssl req -x509 -key client-ca-private-key.pem -out client-ca-cert.pem -days 365 Country Name (2 letter code) [AU]: State or Province Name (full name) [Some-State]: Locality Name (eg, city) []: Organization Name (eg, company) [Internet Widgits Pty Ltd]: Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []:client-ca Email Address []: # 署名のための準備 $ mkdir -p demoCA/newcerts \u0026amp;\u0026amp; touch demoCA/index.txt $ echo 01 \u0026gt; ./demoCA/serial # クライアント証明書を作成(クライアント認証局で署名する) # PKCS#12形式のものも作成しておく $ openssl genpkey -algorithm RSA -out client-private-key.pem $ openssl req -new -key client-private-key.pem -out client-csr.pem Country Name (2 letter code) [AU]: State or Province Name (full name) [Some-State]: Locality Name (eg, city) []: Organization Name (eg, company) [Internet Widgits Pty Ltd]: Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []:client Email Address []: A challenge password []: An optional company name []: $ openssl ca -in client-csr.pem -out client-cert.pem -keyfile client-ca-private-key.pem -cert client-ca-cert.pem -days 365 Sign the certificate? [y/n]:y 1 out of 1 certificate requests certified, commit? [y/n]y $ openssl pkcs12 -export -clcerts -in client-cert.pem -inkey client-private-key.pem -out client-cert.p12 Enter Export Password: fugafuga Verifying - Enter Export Password: fugafuga # コンテナから出ると鍵と証明書が作成されている $ exit $ ls -1 client-ca-cert.pem client-ca-private-key.pem client-cert.p12 client-cert.pem client-csr.pem client-private-key.pem demoCA server-cert.pem server-private-key.pem 今回は鍵と証明書をたくさん使っていて後で混乱しそうなのでここに内容をまとめておく.\nサーバー認証局の証明書(server-cert.pem) サーバー証明書に署名した認証局の証明書 今回はオレオレ証明書なのでサーバー証明書と同一だが, ちゃんとした認証局(CA)によってサーバー証明書を発行した場合は認証局の証明書を使う. サーバー認証局の秘密鍵(server-private-key.pem) サーバー証明書に署名した認証局の秘密鍵 今回はオレオレ証明書なのでサーバー秘密鍵と同一 このあとは使わない サーバー証明書(server-cert.pem) サーバー(Prometheus)の身元を証明する証明書. 今回はオレオレ証明書を使う. ホスト名はprometheus.hogehoge.comとした(オレオレ証明書なので任意の名前) サーバー秘密鍵(server-private-key.pem) サーバー(Prometheus)に持たせる秘密鍵 クライアント認証局の証明書(client-ca-cert.pem) クライアント証明書に署名した認証局の証明書 クライアント認証局の秘密鍵(client-ca-private-key.pem) クライアント証明書に署名した認証局の秘密鍵 このあとは使わない クライアント証明書(client-cert.pem, client-cert.p12) クライアントの身元を証明する証明書 クライアント(Prometheus, Grafana, User)ごとに分けてもいいけど, 面倒なので全部同じものを使う クライアント秘密鍵(client-private-key.pem) クライアントに持たせる秘密鍵 クライアントのCSR(client-csr.pem) クライアント証明書を発行するときに使ったCSR このあとは使わない ついでにブラウザでも動作確認できるように, Macでサーバー証明書を信頼する設定とクライアント証明書を持たせる設定を行う.\nFinderからclient-cert.p12とserver-cert.pemをそれぞれダブルクリックして,\n証明書をキーチェーンアクセスで信頼するように設定する.\n最後に, 今回はサーバー証明書で実在しないドメイン(prometheus.hogehoge.com)を指定しているので,\nMacの/etc/hostsに名前解決の設定を記述しておく.\n# /etc/hostsにprometheus.hogehoge.com(localhostに飛ばす)を追加 $ echo \u0026#34;127.0.0.1 prometheus.hogehoge.com\u0026#34; | sudo tee -a /etc/hosts 以上で証明書とか秘密鍵の準備は完了.\nPrometheus単独でクライアント認証をかける Prometheus自体にクライアント認証の仕組みがあるので1,\nサーバー証明書とサーバー秘密鍵(HTTPS化してサーバーの身元を証明するのに使用),\nクライアント認証局の証明書(クライアントの身元を確認するのに使用)を持たせる.\nクライアント認証の設定はweb-config.ymlに記述する.\nPrometheus自身のメトリクスを取得する際にもクライアント認証を突破する必要があるので,\nクライアント証明書とクライアント秘密鍵(クライアントの身元を証明するのに使用),\nサーバー認証局の証明書(HTTPSサーバーの身元を確認するのに使用)も持たせることにする.\n(Prometheus自身のメトリクスを取得しない場合は不要)\nクライアント認証を突破するための設定はprometheus.ymlのscrape_configs2に記述する.\nあとは指定のパスにそれぞれの秘密鍵, 証明書ファイルを配置する.\n今回はDockerで起動するのでパスを指定してマウントしてあげれば良い.\n以上で準備ができたので, いよいよDockerで起動する.\n# ディレクトリの状態(使わないものは消している) $ ls -1 client-ca-cert.pem client-cert.pem client-private-key.pem docker-compose.yml prometheus.yml server-cert.pem server-private-key.pem web-config.yml # コンテナを起動する $ docker compose up -d --force-recreate --remove-orphans 動作確認のため,\nChromeで https://prometheus.hogehoge.com を開く.\nMacにクライアント証明書をもたせたため, 問題なく閲覧できた.\n今度はクライアント証明書を使わずに https://prometheus.hogehoge.com を開いてみる.\n要求されたクライアント証明書を提示しなかったため, 閲覧ができない.\n以上でPrometheusにクライアント認証がかかっていることを確認できた.\n# コンテナを終了しておく $ docker compose down Prometheus+nginxでクライアント認証をかける Prometheusの前段にnginx(リバースプロキシ)を用意して, そこでクライアント認証をかけることもできる.\nnginxにサーバー証明書とサーバー秘密鍵(HTTPSサーバーの身元を証明するのに使用),\nクライアント認証局の証明書(クライアントの身元を確認するのに使用)を持たせる.\n今回はHTTPS化と認証まわりの設定をhttps.confに記述する.\n今回はクライアント認証を行うポート(443)と別に認証をかけないポート(44433)も用意してみる.\nこの場合Prometheus自体にはクライアント認証をかけないのでweb-config.ymlは不要,\nさらに自身のメトリクスを取得する場合でもクライアント証明書とクライアント秘密鍵は不要となる.\n(したがってprometheus.ymlも自分で作らずデフォルトのものを使用する)\nあとは各種ファイルをnginxのコンテナにマウントして起動する.\n(先程とは異なり, Prometheusにファイルを持たせないなど設定が変わっていることに注意)\n# ディレクトリの状態(使わないものを消している) $ ls -1 client-ca-cert.pem docker-compose.yml https.conf server-cert.pem server-private-key.pem # コンテナを起動する $ docker compose up -d --force-recreate --remove-orphans Chromeでhttps://prometheus.hogehoge.comを開く.\nクライアント証明書を提示しなかったので認証に失敗した.\n(クライアント証明書を要求された際に\u0026quot;OK\u0026quot;を選択すると認証に成功する)\nこの状態で次はhttps://prometheus.hogehoge.com:44433を開く.\n44433ポートにはクライアント認証をかけていないため, そのまま開くことができた.\nこんな感じの出し分けをPrometheus単独でやるのは難しいが,\nnginxを使うと比較的カンタンに実現できるし, Prometheus自身のメトリクスを取得するためにクライアント証明書などを持たせる必要もなくなる.\nクライアント認証以外の認証方式も設定しやすいので,\n個人的にはこちらのほうがすき.\n# コンテナを終了しておく $ docker compose down おわり Prometheusにクライアント認証をかけてみた.\nセキュリティ要件でPrometheusへのアクセスに認証が必要な場合は,\n基本は前段のnginxで認証設定をかけてクライアント側(Grafanaとか)に認証情報を持たせてアクセスするのが良いと思う.\nおまけ https://prometheus.io/docs/prometheus/latest/configuration/https/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2021-06-05T00:00:00Z","image":"/post/2021-06-05-prometheus-client-auth/sotochan.jpg","permalink":"/post/2021-06-05-prometheus-client-auth/","title":"Prometheusにクライアント認証をかける"},{"content":"まとめ 誕生日 記念写真 誕生日 そとちゃんは5月1日でたぶん5さいになった.\n元野良なので本当の誕生日はわからないけど,\nうちに来た記念日と病院で推測してもらった日にちから5月1日生まれということにしている.\n今年は気合を入れて猫用の誕生日ケーキも買った.\nそとちゃんも大喜び.🤗\nたぶん5さいになった🎉 pic.twitter.com/OnaNLYQUGJ\n\u0026mdash; ずみし (@uzimihsr) May 1, 2021 \u0026hellip;だったのは最初の一口だけで,\nその後はまたたびをふりかけても見向きもしなかった.😭\nお口に合わなかったらしい.\nお気に召さなかった😭 pic.twitter.com/W3XCohhG4F\n\u0026mdash; ずみし (@uzimihsr) May 1, 2021 ケーキはダメだったけど, 誕生日プレゼントに用意したおもちゃは喜んでくれた.\nキャッチ・ミー・イフ・ユー・キャン 2 猫と音符\nもともと評判が良いおもちゃだったけど, そとちゃんもかなり喰い付きが良かった.\n電池を入れっぱなしにすると延々と遊び続ける.\n成猫のそとちゃんが電池切れまで遊ぶのは初めてみた.\nねこもおもちゃも電池切れ pic.twitter.com/8sTYWa9qBn\n\u0026mdash; ずみし (@uzimihsr) May 9, 2021 気に入ってくれてよかった.\n記念写真 5さいになった記念に写真屋さんでそとちゃんを撮ってもらった.\nそとちゃんは妙に肝が据わっているので初めての場所でもガンガン探検しちゃう.\n楽しすぎて撮影用のポジションになかなか止まってくれない.\nこんなこともあろうかと持ってきていたお気に入りのねずみのおもちゃで目線を釣ってもらい,\nなんとか撮影できた.\nそれでもそとちゃん単独だとすぐに動いちゃって枚数が確保できないとのことで後半は俺も一緒に撮ってもらった.\nはしゃいで相当疲れたのか, うちに帰ってからはすぐに寝てしまった.💤\nつかれた pic.twitter.com/vQjeA8us06\n\u0026mdash; ずみし (@uzimihsr) May 16, 2021 がんばってくれてえらかった.\nおわり そとちゃんが5さいになった. めでたい.🎉\nそとちゃんからしたらいきなり変なものを食わされたり,\n知らないところを探検したいのに無理やり写真を撮られたりでいい迷惑だったかもしれない.\nそれでも, そとちゃんはたぶん俺より長くは生きられないのでできるだけお祝いとか記録に残ることをしたい.\n(これは俺のエゴだとわかっているけども\u0026hellip;)\n仕方なく(あるいはおもちゃやおやつに釣られながら)付き合ってくれるそとちゃんには感謝しかないし,\nそとちゃんが6さいの誕生日を元気に迎えられるように俺もまた1年頑張ろうと思った.\n\u0026hellip;猫用ケーキは二度と買わない.\nおまけ ","date":"2021-06-01T00:00:00Z","image":"/post/2021-06-01-sotochan/sotochan.jpg","permalink":"/post/2021-06-01-sotochan/","title":"5月のそとちゃんまとめ(2021)"},{"content":"まとめ dockertestを使うとGoのコード内からテスト対象のDBをDockerコンテナとして起動して接続することができる.\nこれを使ってDBを都度立ち上げて, 実際にレコード操作の検証をするテストを書いてみた.\n環境 macOS Big Sur 11.2.3 go version go1.15.5 darwin/amd64 dockertest v3.3.5 Docker Desktop for Mac Version 3.3.0 Docker Engine Version 20.10.5 やりかた この前はDBをモックしたテストを試したけど,\nやっぱり実際にテスト用のDBを立てて期待値が得られるか検証したいときもある.\nテスト対象となるのは前回同様こんな感じの処理.\n今回はGoからDockerをいじることができるdockertestを使ってテスト用DBを起動し,\nそれに接続してテストを行う.\nまずはテーブル定義を記述したファイルをつくる.\nついでにMySQLの設定ファイルを作っておく.\n次に公式の例1を参考に, MySQL2のコンテナを立ち上げる関数を書いてみる.\n頑張ったのはcreateContainer()でRunOptions3を使ってコンテナ起動時に細かいオプションを指定したり必要な設定ファイルをマウントできるようにしたところ.\nこうしておくことで大体のDBの設定をこのテストコードに封じ込めることができる.\ncloseContainer()を呼び忘れるとテストが終わってもコンテナが残ってしまうので注意する(deferで呼んでおくのが良さそう).\n各機能のテストではそれぞれこの関数を呼び出してテスト用のDBを作成する.\n前回同様Create()についてテストを作るとこんな感じ.\n一応テスト対象の関数以外のDB操作(挿入したレコードを取得するときなど)もRead()などの自分で定義している関数ではなく都度SQLを実行するようにした.\n最終的なテストコードはこんな感じ.\n最後にテストを実行してみる.\n# ディレクトリの状態 $ tree . . ├── go.mod ├── go.sum ├── main.go ├── main_test.go ├── my.cnf └── todo.sql # テスト実行 $ go test -v ./... === RUN TestCreateWithDB === RUN TestCreateWithDB/レコードが正しく追加されることのテスト [mysql] 2021/05/13 23:41:36 packets.go:37: unexpected EOF [mysql] 2021/05/13 23:41:36 packets.go:37: unexpected EOF [mysql] 2021/05/13 23:41:36 packets.go:37: unexpected EOF --- PASS: TestCreateWithDB (27.14s) --- PASS: TestCreateWithDB/レコードが正しく追加されることのテスト (27.14s) === RUN TestReadWithDB === RUN TestReadWithDB/レコードが正しく取得できることのテスト [mysql] 2021/05/13 23:42:03 packets.go:37: unexpected EOF [mysql] 2021/05/13 23:42:03 packets.go:37: unexpected EOF [mysql] 2021/05/13 23:42:03 packets.go:37: unexpected EOF --- PASS: TestReadWithDB (26.95s) --- PASS: TestReadWithDB/レコードが正しく取得できることのテスト (26.95s) === RUN TestUpdateWithDB === RUN TestUpdateWithDB/レコードが正しく更新できることのテスト [mysql] 2021/05/13 23:42:30 packets.go:37: unexpected EOF [mysql] 2021/05/13 23:42:30 packets.go:37: unexpected EOF [mysql] 2021/05/13 23:42:30 packets.go:37: unexpected EOF --- PASS: TestUpdateWithDB (27.15s) --- PASS: TestUpdateWithDB/レコードが正しく更新できることのテスト (27.15s) === RUN TestDeleteWithDB === RUN TestDeleteWithDB/レコードが正しく削除できることのテスト [mysql] 2021/05/13 23:42:58 packets.go:37: unexpected EOF [mysql] 2021/05/13 23:42:58 packets.go:37: unexpected EOF [mysql] 2021/05/13 23:42:58 packets.go:37: unexpected EOF --- PASS: TestDeleteWithDB (28.51s) --- PASS: TestDeleteWithDB/レコードが正しく削除できることのテスト (28.51s) PASS ok github.com/uzimihsr/golang-db-test\t110.730s コンテナを立ち上げてから疎通できるまでにちょっと時間がかかるのでunexpected EOFのエラーが何回か出てるけど, 内部でリトライをしてるので問題ないはず.\n都度Dockerの操作をしてるせいでかなり時間がかかっているので, t.Parallel()4とかを使って並列実行させたほうが良いかも.\nおわり dockertestを使ってテスト用のMySQLコンテナをGoのコードから立てて操作してみた.\n別にDBに限らず外部とのやりとりをする部分は対象をDocker化できるなら何でも使えちゃいそう.\nただしテスト環境でGo以外にDockerが必要になったり,\nコンテナの立ち上げにかなり時間がかかるなどクセも強いのでどこまでやるかはテスト要件と相談して使いたい.\nおまけ github.com/ory/dockertest/blob/v3/examples/MySQL.md\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhub.docker.com/_/mysql\u0026#160;\u0026#x21a9;\u0026#xfe0e;\npkg.go.dev/github.com/ory/dockertest\u0026#160;\u0026#x21a9;\u0026#xfe0e;\ngithub.com/golang/go/wiki/TableDrivenTests\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2021-05-14T00:00:00Z","image":"/post/2021-05-14-golang-test-with-dockertest/sotochan.jpg","permalink":"/post/2021-05-14-golang-test-with-dockertest/","title":"GoでSQLのユニットテストを書く(dockertest)"},{"content":"まとめ シャンプー ひざ ついに捉えた シャンプー 定期的にブラッシングはしてるものの,\nそれでも静電気と抜け毛がすごかったので約1年半ぶりにそとちゃんにお風呂に入ってもらった.\nご機嫌なところに大変残念なお知らせですが今日はお風呂に入ります pic.twitter.com/pmsgsHc8KQ\n\u0026mdash; ずみし (@uzimihsr) April 4, 2021 そとちゃんはお風呂場がけっこう好きで, 普段も湯船のフタの上でごろごろしてたりする.\nこの日もお風呂場の浴室乾燥を入れて暖かくなったところに自分から出向いてきた.\n(洗われるとは思っていない)\nbefore pic.twitter.com/LkLXZyI7Dv\n\u0026mdash; ずみし (@uzimihsr) April 4, 2021 まずはぬるめのシャワーで毛を濡らす.\n(そとちゃんはねこだけどシャワーは平気. えらい!)\nつぎに猫用シャンプーで体を洗っていく.\nそとちゃんはここが一番つらかったらしく, 暴れはしないけどわんわん鳴いていた.\nシャンプー pic.twitter.com/qsmDLQloSi\n\u0026mdash; ずみし (@uzimihsr) April 4, 2021 再びシャワーでシャンプーを洗い流す.\nこのあたりで開始から20分くらいが経ち, そとちゃんも心が折れて鳴き声が弱々しくなった. かわいそう.\nすすぎ pic.twitter.com/UK4abqsbwt\n\u0026mdash; ずみし (@uzimihsr) April 4, 2021 でもしんどいのはここまで. あとは乾かしていくだけ.\n浴室乾燥とタオルで乾かす pic.twitter.com/esBDF7cpZ7\n\u0026mdash; ずみし (@uzimihsr) April 4, 2021 タオルで水気を吸い取り, ドライヤーで仕上げたら毛がふわふわに元通り.\n(そとちゃんはドライヤーも平気. えらい!)\nごほうびのちゅーるをあげて終了.\nafter pic.twitter.com/6BSAmgPK4c\n\u0026mdash; ずみし (@uzimihsr) April 4, 2021 そとちゃんは結局一度も暴れなかった. えらい.😊\n(シャンプー後に毛づくろいしすぎて次の日珍しく毛玉を吐いた)\n毛玉吐いた pic.twitter.com/xirdjVBh5q\n\u0026mdash; ずみし (@uzimihsr) April 5, 2021 ひざ そとちゃんは最近ほぼ毎日ひざに乗ってくる.\nあまえんぼ pic.twitter.com/3RqRbh12ni\n\u0026mdash; ずみし (@uzimihsr) April 7, 2021 俺がソファでだらだらしてるときによく乗ってくる.\nこの体勢だとねこをなでながらスマホがいじれちゃうので時間が爆速で溶ける.\n支配者 pic.twitter.com/kcNNtHVRyK\n\u0026mdash; ずみし (@uzimihsr) April 16, 2021 ちょっとお留守番したときなんかも乗ってくるけど,\nこのときはスマホをいじると不機嫌になるのでやらない.\nおるすばんしたねこは膝を占領する権利がある pic.twitter.com/Edj5otupQV\n\u0026mdash; ずみし (@uzimihsr) April 18, 2021 かわいい〜\nついに捉えた ついにカメラが決定的瞬間を捉えた!!!\n現行犯 pic.twitter.com/Gdgjkor4zr\n\u0026mdash; ずみし (@uzimihsr) April 24, 2021 めっちゃ悪い顔してる😭😭😭\nスパゲッティみたいだ\u0026hellip;😭\nおわり そとちゃんはえらいねこなので, 久しぶりのお風呂も頑張ってくれた.\n濡れたらいつもより痩せて見えるかとおもったけど, そんなことはなかった\u0026hellip;\nおまけ ","date":"2021-05-12T00:00:00Z","image":"/post/2021-05-12-sotochan/sotochan.jpg","permalink":"/post/2021-05-12-sotochan/","title":"4月のそとちゃんまとめ(2021)"},{"content":"まとめ GoでSQLのテストを書くときはgo-sqlmockを使うとデータベースのモックがかんたんに作れる.\n環境 macOS Big Sur 11.2.3 go version go1.15.5 darwin/amd64 go-sqlmock v1.5.0 やりかた こんな感じでDB(MySQL)のテーブル操作(CRUD)を行う関数について, ユニットテストを書きたくなった.\nテスト用のDBを立てて実際に操作しても良いんだけど,\nCIするときに面倒だったりするのでできればGoだけでDBをモックして完結したテストを書きたい.\nこんなときにはgo-sqlmockが使える.\n公式README1を参考にしながら関数Create()についてテストを書いてみるとこんな感じ.\nArrangeでDBのモックを作成し, 想定される引数に対する挙動を設定している.\nエラーの有無や返す値の内容などもmock.Expect~().With~().With~()2で細かく設定できる.\n同様にRead(), Update(), Delete()についても書いていく.\n最終的なテストコードはこんな感じ.\n最後にテストを実行してみる.\n$ tree . . ├── go.mod ├── go.sum ├── main.go └── main_test.go # テスト実行 $ go test -v ./... === RUN TestCreate === RUN TestCreate/Createが成功するケース === RUN TestCreate/Createが失敗するケース --- PASS: TestCreate (0.00s) --- PASS: TestCreate/Createが成功するケース (0.00s) --- PASS: TestCreate/Createが失敗するケース (0.00s) === RUN TestRead === RUN TestRead/Readが成功するケース === RUN TestRead/Readが失敗するケース(QueryRowでエラー) === RUN TestRead/Readが失敗するケース(Scanでエラー) --- PASS: TestRead (0.00s) --- PASS: TestRead/Readが成功するケース (0.00s) --- PASS: TestRead/Readが失敗するケース(QueryRowでエラー) (0.00s) --- PASS: TestRead/Readが失敗するケース(Scanでエラー) (0.00s) === RUN TestUpdate === RUN TestUpdate/Updateが成功するケース === RUN TestUpdate/Updateが失敗するケース --- PASS: TestUpdate (0.00s) --- PASS: TestUpdate/Updateが成功するケース (0.00s) --- PASS: TestUpdate/Updateが失敗するケース (0.00s) === RUN TestDelete === RUN TestDelete/Deleteが成功するケース === RUN TestDelete/Deleteが失敗するケース --- PASS: TestDelete (0.00s) --- PASS: TestDelete/Deleteが成功するケース (0.00s) --- PASS: TestDelete/Deleteが失敗するケース (0.00s) PASS ok github.com/uzimihsr/golang-db-test\t0.170s できた.\nおわり go-sqlmockを使うことで, テスト用のDBを立ち上げることなくGoでSQLのテストができた. べんり.\nおまけ 公式README\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nsqlmock - pkg.go.dev\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2021-04-30T00:00:00Z","image":"/post/2021-04-30-golang-test-with-go-sqlmock/sotochan.jpg","permalink":"/post/2021-04-30-golang-test-with-go-sqlmock/","title":"GoでSQLのユニットテストを書く(go-sqlmock)"},{"content":"まとめ おもちゃ 毛布 ブラッシング おもちゃ 3月は新しいおもちゃをいくつか買った.\nまずはねこじゃらし.\nじゃれ猫　トリオ\nなんとこれ, おもちゃの部分がそとちゃんが大好きなねずみのおもちゃにそっくり.\n同じメーカーだから(?)かはわからないけど振るとシャカシャカ音がするのも同じで,\n当然そとちゃんは大興奮.\nうおお pic.twitter.com/IpdQslzcZm\n\u0026mdash; ずみし (@uzimihsr) March 19, 2021 これならヒモがついてるのでそとちゃんが失くすこともないので安心.\n買ってよかった.\n次に買ったのはペットショップでバラ売りされていたドギツい色のねずみのおもちゃ.\n新しいねずみ pic.twitter.com/8fksuAyoNh\n\u0026mdash; ずみし (@uzimihsr) March 25, 2021 これは大失敗.\n他のねずみと同じく軽くて振るとシャカシャカ音はするんだけど,\n音が少し小さいのが悪いのか色が悪いのかそとちゃんのお気に召さなかった.\n数回遊んでおもちゃ箱(戻ってこない)行き.\nさらに期待はずれだったのはまたたびボール.\nじゃれ猫　またたびタックル\n中にまたたびの粒?が入っていて如何にもねこが食いつきそう.\nまたたびがついてるおもちゃは手でガッツリ押さえてまたたび部分だけベロベロ舐めちゃうジャンキーのそとちゃんだけど,\nこの形ならねこの手じゃ押さえづらいし飽きずに追いかけてくれるかな\u0026hellip;という期待もあった.\n結局最初の数分は匂いで興奮してたんだけど,\n何回か押さえようとして失敗したらすぐ興味をなくしてしまった.\n例のブツを効率良く摂取したいそとちゃんには合わなかったらしい.🌿\nねこの好みは難しい\u0026hellip;\n毛布 3月上旬はまだ寒かったので俺のベッドに茶色の毛布を敷いていて,\nこの毛布がそとちゃんのお気に入りになってしまった.\nひっくり返っておでこをつけちゃうくらいの気に入りよう.\nかわいいポーズしてる pic.twitter.com/952eTB4SXU\n\u0026mdash; ずみし (@uzimihsr) March 2, 2021 流石に暖かくなってきたので片付けようとしたんだけど,\nなかなか許可が降りず\u0026hellip;\nベッドに敷いてたのを畳んでソファに置いたらすぐに占拠されてしまった.\n毛布片付けの許可が出ない pic.twitter.com/icem8UyvPy\n\u0026mdash; ずみし (@uzimihsr) March 24, 2021 強い意志を感じる pic.twitter.com/pwUdYB5D14\n\u0026mdash; ずみし (@uzimihsr) March 29, 2021 しかたがないのでソファにそのまま置いておいて,　俺が座るときだけどかすようにしている.\n夏まで置いておくわけにもいかないので, 頃合いを見計らって片付けたい.\nブラッシング 部屋が乾燥してたのと毛布でゴロゴロしまくったのが原因でそとちゃんが帯電してしまい,\n撫でるたびに静電気でバチバチしてたのでブラッシングをした.\nそとちゃんはブラッシングが大嫌いで,\nあまりにも嫌がるので普段は2週に1回くらいの頻度でしかやっていない.\nブラシをかける→我慢できなくなって逃げる→おもちゃとおやつで機嫌を取る→\b捕まえてブラシをかける\u0026hellip;\nを10回くらい繰り返すのでいつもだいたい1時間くらいかかるんだけど,\n今回は特によく逃げるので長期戦だった.\n途中でおひるねタイムまで入る始末.\nブラシから逃げ回って疲れ果てた pic.twitter.com/c0TJQpRXH2\n\u0026mdash; ずみし (@uzimihsr) March 13, 2021 かわいそうだけど起こしてなんとか終了.\n約2時間の激戦で過去最大級の抜け毛が取れた.\n2時間 pic.twitter.com/CSQyDdXobb\n\u0026mdash; ずみし (@uzimihsr) March 13, 2021 丸めたらピンポン球くらいのサイズになったのでそとちゃんのおもちゃになった.\nおわり 先月に引き続きまたも遅筆. 良くない.\n時間を見つけて書いてはいるけどなかなかまとめきれずに公開できない.\n4月はそとちゃんがお風呂に入ったりといろいろあったので早めに書きたい.\nおまけ ","date":"2021-04-23T11:08:42+09:00","image":"/post/2021-04-23-sotochan/sotochan.jpg","permalink":"/post/2021-04-23-sotochan/","title":"3月のそとちゃんまとめ(2021)"},{"content":"まとめ 揺れた 猫の日 つめとぎ GERO クソデカトイレマット 揺れた 2月はかなりでかい地震があった.\nうちもかなり揺れたんだけど, そとちゃんは本棚の上でぐーすか寝ていた\u0026hellip;\n危機感がまるで感じられない.\nねこが地震に気づかず寝ていたので少しだけお説教した pic.twitter.com/P8KKo9pQvT\n\u0026mdash; ずみし (@uzimihsr) February 13, 2021 万が一のことを考えて一応避難の準備もしたけど,\nそとちゃんはお出かけ大好きねこなのですんなりキャリーに入ってくれた.\n協力的で助かる〜 pic.twitter.com/thPoCTHDvQ\n\u0026mdash; ずみし (@uzimihsr) February 13, 2021 結局避難せずに済んだけどごはんとトイレの道具を準備するのに結構時間がかかったし,\n災害への備えが全然できてなかったことを実感した\u0026hellip;\n猫の日 2/22は猫の日だったらしい.\n去年に続けて今年もイエローハットが面白そうなキャンペーンをやっていたので,\nそとちゃんには交通にゃん全クイズに挑戦してもらった.\n結果は\u0026hellip;?\n😭\nつめとぎ 今まで使っていたつめとぎがぼろぼろだったので, 全く同じものを買った.\nつめとぎボロボロなので2台目を買いました pic.twitter.com/bLhtsCQ6Uh\n\u0026mdash; ずみし (@uzimihsr) February 22, 2021 Gari Gari Wall Scratcher PLUS\nこれはねこが立った姿勢でつめとぎができるスグレモノで, そとちゃんのお気に入り.\n好きすぎて組み立て中にバリバリしちゃう.\n完成を待てない pic.twitter.com/08YHEILJs6\n\u0026mdash; ずみし (@uzimihsr) February 22, 2021 新品と古いのを比べると一目瞭然で, そとちゃんがどれだけ今までバリバリしてくれたかがよくわかる.\n職人ねこは造り終えたものに興味は無い\nまた次の現場へ向かう pic.twitter.com/RrUGGUGzFx\n\u0026mdash; ずみし (@uzimihsr) February 22, 2021 ちなみにつめとぎが新しくなってもそとちゃんはソファをバリバリし続けている\u0026hellip;\nどうして😭\nGERO 久しぶりにそとちゃんが吐いた.\n早朝の5時くらいにかなり大きめの「オエッ、ゴポッ」とした音がしたのでびっくりして飛び起きた.\n現場を見てみるとけっこうガッツリやっていた.\n(画像はモザイクをかけてるけど, 直前に食べたらしいカリカリと胃液?が混ざった茶色いGEROが撒き散っていた)\nそとちゃんは普段滅多に吐かない(誤飲騒動以来全く吐いてなかった)ので俺はもうパニック.\n急いで獣医さんに電話したけど,\n吐いたのが1回で終わっていたのと吐いた直後も食欲があったことから急患にはかからず家で様子を見ることに.\n早朝に半年ぶりのGEROを吐いてしもべを叩き起こしたねこ\n余裕ぶっこいてるし食欲も遊ぶ元気もあるので獣医さんと相談した上で様子見 pic.twitter.com/dZfoQu162E\n\u0026mdash; ずみし (@uzimihsr) February 17, 2021 結局その後は何も起こらず元気に運動会をしていたので, 病院にも行っていない.\n大変なことにならなくてよかった.\nごはんめっちゃ食べよる pic.twitter.com/6ze6P56hdq\n\u0026mdash; ずみし (@uzimihsr) February 17, 2021 どうして吐いたのかはわからずじまいだけど,\nフードの形がわかるくらいのGEROだったので空腹状態で食べ急いで胃がびっくりしたのかと推測している.🤔\n(GEROの前日は夕方のカリカリをガッツリ残していたのでおやつを抜いていた)\nGEROカーペットもすぐに外して洗濯したので今ではわからないくらいきれいになった.\nタイルカーペットにしておいてよかった.\nクソデカトイレマット そとちゃんは掃除がめっちゃ便利なロボットトイレを使ってくれている. えらい.😊\nが, このトイレは横からの入り口が大きく開いていて,\nそとちゃんはうんちした後にハイテンションで飛び出てくるので周りに猫砂が飛び散る.😭\n一応小さめのトイレマットを敷いてたんだけど,\nそとちゃんの射出角度(?)によってはマットを超えちゃったりしてあんまり意味がなかった.\nそこでこのクソデカトイレマットを購入.\nPetio necoco 猫トイレマット ワイド\n今度はかなりの範囲がカバーできるようになった.\n飛び散った猫砂を裸足で踏んで悲しい思いをすることがなくなってうれしい.\nおわり 2月はそとちゃんも俺もいろいろありすぎて, 書くのが遅くなってしまった\u0026hellip;\n3月も既に新しいおもちゃをいくつか買ったりしているので, はやめに書いておきたい.\nおまけ ","date":"2021-03-21T00:00:00Z","image":"/post/2021-03-21-sotochan/sotochan.jpg","permalink":"/post/2021-03-21-sotochan/","title":"2月のそとちゃんまとめ(2021)"},{"content":"やったことのまとめ Prometheusと同様にGrafanaもHTTPS化してみた.\nPrometheusと同様にGrafana自体に証明書と秘密鍵をもたせる方法とHTTPS対応したリバースプロキシを使う方法の2通りを試した.\nつかうもの macOS Mojave 10.14 Dockerのホストとして使用, 動作確認用のブラウザもこちらで起動する Google Chrome バージョン: 88.0.4324.150（Official Build） （x86_64） Docker Desktop for Mac Version 3.1.0 Docker Engine Version 20.10.2 docker-compose version 1.27.4 Grafana (Docker) version 7.3.7 nginx (Docker) version 1.18.0 openssl version 1.1.1d やったこと 秘密鍵とオレオレ証明書の作成 Grafanaに証明書と秘密鍵を持たせる nginxでリバースプロキシする 秘密鍵とオレオレ証明書の作成 前回と全く同じ手順で秘密鍵と証明書を作成する.\n# cert.pemはSANsでhogehoge.comが指定されたSSL証明書 $ openssl x509 -in cert.pem -noout -text Certificate: Data: Version: 3 (0x2) Serial Number: 24:4c:ec:40:cc:94:5c:a2:af:30:6a:e9:7d:5c:17:28:5e:fb:ec:fc Signature Algorithm: sha256WithRSAEncryption Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=hogehoge.com Validity Not Before: Feb 10 16:13:01 2021 GMT Not After : Mar 12 16:13:01 2021 GMT Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=hogehoge.com ... X509v3 extensions: ... X509v3 Subject Alternative Name: DNS:hogehoge.com, DNS:fugafuga.com ... # private-key.pemはcert.pemに紐づく秘密鍵 $ openssl rsa -in private-key.pem -text -noout Private-Key: (2048 bit) modulus: ... 作成したオレオレ証明書はMacで信頼する設定にしておく.\nGrafanaに証明書と秘密鍵を持たせる Grafanaは元からHTTPS化に対応しているので, SSL証明書とその秘密鍵をもたせる1だけで良さそう.\n証明書(cert_file)と鍵の場所(cert_key)などはgrafana.iniで設定できる.\nあとはこのgrafana.iniと証明書, 秘密鍵を適切な場所に配置してGrafanaを起動する.\n今回はDocker Composeで試すが, 非コンテナ環境でもやることは同じはず.\n$ tree . . ├── cert.pem ├── docker-compose.yml ├── grafana.ini └── private-key.pem # コンテナ起動 $ docker-compose up -d --force-recreate --remove-orphans # localhostの443番ポートがGrafanaコンテナの443に割り当てられている $ docker-compose ps Name Command State Ports ---------------------------------------------------------- grafana /run.sh Up 3000/tcp, 0.0.0.0:443-\u0026gt;443/tcp https://hogehoge.comを開く.\n(hogehoge.comでlocalhostにアクセスするためには/etc/hostsを編集しておく)\nGrafanaがHTTPS化されていることが確認できた.\nかんたん.😊\nnginxでリバースプロキシする もちろんPrometheusと同様にHTTPS化したnginxでリバースプロキシすることもできるはず.\n前回と同様にnginxに証明書と鍵を持たせて, Grafana側でリバースプロキシ用の設定を入れてみる.\n$ tree . . ├── cert.pem ├── docker-compose.yml ├── grafana.ini ├── https.conf └── private-key.pem # コンテナ起動 $ docker-compose up -d --force-recreate --remove-orphans # localhostの443番ポートがnginxコンテナの443に割り当てられている $ docker-compose ps Name Command State Ports ------------------------------------------------------------------------------- grafana /run.sh Up 3000/tcp nginx /docker-entrypoint.sh ngin ... Up 0.0.0.0:443-\u0026gt;443/tcp, 80/tcp Grafanaとnginxが起動したらhttps://hogehoge.com/grafana/を開く.\n先ほどと同様にHTTPSでGrafanaが公開されていることが確認できた.\nやったぜ.\nおわり GrafanaをHTTPS化してみた.\nGrafana自体にHTTPS対応機能があるので設定はわりとカンタンだった.\nnginxを使う場合もPrometheusをHTTPS化したときと同じようにできたのでそんなにつまづくところはなかったと思う.\nおまけ https://grafana.com/docs/grafana/latest/administration/configuration/#cert_file\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2021-02-15T00:00:00Z","image":"/post/2021-02-15-grafana-tls-ssl/sotochan.jpg","permalink":"/post/2021-02-15-grafana-tls-ssl/","title":"GrafanaをHTTPS化する"},{"content":"やったことのまとめ PrometheusはデフォルトだとHTTP(暗号化なし)で公開されるが, セキュリティの観点からどうしてもHTTPSで動かしたい場合がある.\n今回はnginxを使ってリバースプロキシする方法とPrometheus自体をHTTPS化する方法の2通りの方法を試してみた.\nつかうもの macOS Mojave 10.14 Dockerのホストとして使用, 動作確認用のブラウザもこちらで起動する Google Chrome バージョン: 88.0.4324.150（Official Build） （x86_64） Docker Desktop for Mac Version 3.1.0 Docker Engine Version 20.10.2 docker-compose version 1.27.4 Prometheus (Docker) version 2.24.1 nginx (Docker) version 1.18.0 openssl version 1.1.1d やったこと 秘密鍵とオレオレ証明書の作成 nginxを挟んでHTTPS化する Prometheus単独でHTTPS化する 秘密鍵とオレオレ証明書の作成 まずはHTTPSで通信を行うためにopensslで秘密鍵とSSL証明書(オレオレ証明書)を作成する.\n(公的な証明書と鍵のペアがある場合は不要. 文中のhogehoge.comは適切なホスト名に置き換える.)\nオレオレ証明書を作るときは以前のやり方に加えて,\nページをChromeで開いた場合も怒られないようにSANs(Subject Alternative Names)を設定するようにする.\n今回の鍵と証明書の作成に使用するopensslはnginxのDocker imageに入っているものを使う.\n# nginxコンテナの中で実行(カレントディレクトリをマウントしておく) $ docker container run --rm -it -v=\u0026#34;$PWD:/workdir\u0026#34; -w=\u0026#34;/workdir\u0026#34; --entrypoint=/bin/bash nginx:1.18.0 ## ここからコンテナ内 $ openssl version OpenSSL 1.1.1d 10 Sep 2019 # 秘密鍵(private-key.pem)の作成 $ openssl genpkey -algorithm RSA -out private-key.pem # オレオレ証明書(cert.pem)の作成(CSRの作成と署名) # SANsの設定もやっておく(Chrome対応) $ openssl req -x509 -key private-key.pem -out cert.pem -addext \u0026#39;subjectAltName = DNS:hogehoge.com,DNS:fugafuga.com\u0026#39; You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter \u0026#39;.\u0026#39;, the field will be left blank. ----- Country Name (2 letter code) [AU]: State or Province Name (full name) [Some-State]: Locality Name (eg, city) []: Organization Name (eg, company) [Internet Widgits Pty Ltd]: Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []:hogehoge.com Email Address []: # 証明書の中身を確認(SANsが追加されている) $ openssl x509 -in cert.pem -noout -text Certificate: Data: Version: 3 (0x2) Serial Number: 24:4c:ec:40:cc:94:5c:a2:af:30:6a:e9:7d:5c:17:28:5e:fb:ec:fc Signature Algorithm: sha256WithRSAEncryption Issuer: C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = hogehoge.com Validity Not Before: Feb 10 16:13:01 2021 GMT Not After : Mar 12 16:13:01 2021 GMT Subject: C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = hogehoge.com ... X509v3 extensions: ... X509v3 Subject Alternative Name: DNS:hogehoge.com, DNS:fugafuga.com # コンテナから出る $ exit ## ここからホストOS(Mac) # 秘密鍵と証明書ができている $ ls cert.pem private-key.pem 次にSANsに指定したhogehoge.comでlocalhost(127.0.0.1)にアクセスできるよう,\nMac(Prometheusを起動するマシン)の名前解決設定(/etc/hosts)を編集する.\n# /etc/hostsにhogehoge.com(localhost)を追加 $ echo \u0026#34;127.0.0.1 hogehoge.com\u0026#34; | sudo tee -a /etc/hosts 最後に今回作った証明書(cert.pem)は公的な認証局が署名したものではないので,\n証明書をMacのキーチェーンアクセスで開いて常に信頼するよう設定する.\nこれでHTTPS化に必要な証明書の設定は完了.\nnginxを挟んでHTTPS化する 次に公式の手順1に従ってnginxをHTTPS化して, その後ろでPrometheusが動く構成(リバースプロキシ)をつくる.\n今回も例に依ってDocker Composeで試すが, 非コンテナ環境の場合はコンテナ名で名前解決している部分を適切な名前(ホスト名)に置き換えて,\ndocker-compose.ymlで指定している場所に各種の設定ファイルを配置すれば動くはず.\nPrometheus リバースプロキシ用の設定を引数で設定 --web.external-url : 証明書のSANsに指定した名前+リクエストを受け付けるパス --web.route-prefix : /を明示的に指定 nginx PrometheusのリバースプロキシとHTTPSの設定をhttps.confに記述する 秘密鍵, 証明書を指定の場所に配置する コンテナを立ち上げてみる.\n# cert.pemとprivate-key.pemは先ほど作ったオレオレ証明書と秘密鍵 $ tree . . ├── cert.pem ├── docker-compose.yml ├── https.conf └── private-key.pem # コンテナ起動 $ docker-compose up -d --force-recreate --remove-orphans # localhostの443ポートがnginxコンテナに割り当てられている $ docker-compose ps Name Command State Ports ---------------------------------------------------------------------------------- nginx /docker-entrypoint.sh ngin ... Up 0.0.0.0:443-\u0026gt;443/tcp, 80/tcp prometheus /bin/prometheus --config.f ... Up 9090/tcp # オレオレ証明書(cert.pem)を信頼する設定でPrometheus APIを叩いてみる $ curl --cacert ./cert.pem https://hogehoge.com/prometheus/api/v1/label/job/values {\u0026#34;status\u0026#34;:\u0026#34;success\u0026#34;,\u0026#34;data\u0026#34;:[\u0026#34;prometheus\u0026#34;]} コンテナが立ち上がり, hogehoge.comがHTTPSで公開されていることが確認できた.\n念の為Chromeでhttps://hogehoge.com/prometheus/を開いてみる.\nPrometheusがHTTPSで公開されていて, Chromeがオレオレ証明書を信頼していることが確認できた.\nPrometheus単独でHTTPS化する 今までPrometheusをHTTPS化する際は先ほど試したようにリバースプロキシを使うのが推奨されていたのだが,\nなんとversion 2.24からはPrometheus単独でのHTTPS化にも対応した2らしい(まだお試し機能らしいが)ので試してみる.\nまずは新しいHTTPS関連の設定ファイルweb-config.yml3で証明書と秘密鍵の場所を指定する.\n(この他にもクライアント認証やBasic認証も設定できるらしい.)\n次にPrometheusの実行時引数--web.config.fileでweb-config.ymlの場所を指定する.\nまた, hogehoge.comでアクセスを受けられるように--web.external-urlを設定する(nginxを使う場合と異なり/prometheusは不要).\nコンテナを立ち上げてみる.\n# cert.pemとprivate-key.pemは先ほど作ったオレオレ証明書と秘密鍵 $ tree . . ├── cert.pem ├── docker-compose.yml ├── private-key.pem └── web-config.yml # コンテナ起動 $ docker-compose up -d --force-recreate --remove-orphans # localhostの443ポートがPrometheusコンテナに割り当てられている $ docker-compose ps Name Command State Ports ------------------------------------------------------------------------------------ prometheus /bin/prometheus --config.f ... Up 0.0.0.0:443-\u0026gt;443/tcp, 9090/tcp # オレオレ証明書(cert.pem)を信頼する設定でPrometheus APIを叩いてみる $ curl --cacert ./cert.pem https://hogehoge.com/api/v1/label/job/values {\u0026#34;status\u0026#34;:\u0026#34;success\u0026#34;,\u0026#34;data\u0026#34;:[\u0026#34;prometheus\u0026#34;]} 次にChromeでhttps://hogehoge.com/を開くと, nginxを使ったときと同様にPrometheusがHTTPSで公開されていることがわかる.\nやったぜ.\nただしnginxを使う場合には無かった注意点として, 今回はPrometheus自身がHTTPSで起動しているため,\n自身のメトリクス(/metrics)のスクレイプ設定がデフォルトのままだとプロトコルがHTTPのままなのでスクレイプに失敗する.\n今回はとりあえずPrometheusをHTTPS化するのが目的だったので, ここでおわり.\nおわり PrometheusをHTTPS化してみた.\nnginxを使う方法はそれはそう\u0026hellip;という感じだけど, Prometheus単体でHTTPS化できるのは知らなかった.\nただしPrometheus単独でのHTTPS対応はまだお試し機能っぽいのと,\nAlertmanagerはまだ対応してない?みたいなので, 併せて対応する場合はnginxを使うように統一したほうが良さそう.\nおまけ https://prometheus.io/docs/guides/tls-encryption/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://prometheus.io/docs/prometheus/latest/configuration/https/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/prometheus/prometheus/blob/master/documentation/examples/web-config.yml\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2021-02-11T00:00:00Z","image":"/post/2021-02-11-prometheus-tls-ssl/sotochan.jpg","permalink":"/post/2021-02-11-prometheus-tls-ssl/","title":"PrometheusをHTTPS化する"},{"content":"やったことのまとめ 同じ設定のPrometheusを単純に複数台動かして冗長化した 複数台のAlertmanagerをメッシュとして動かして冗長化した PrometheusとAlertmanagerでサービスを監視するのは良いんだけど, 1台ずつで運用してると監視コンポーネント自身がダウンしたときに監視できなくなってしまう.\nだったら監視コンポーネント自体も冗長化すれば可用性を上げられるのでは?と思い試してみた.\nPrometheusの時系列DBの冗長化はかなり難しいっぽいので今回はやらない.\nPrometheusは単純に同じ設定のものを複数同時に動かし, Alertmanagerは複数台を同期してメッシュとして動かすのが良さそう.\n設定ファイルとか\nつかうもの macOS Mojave 10.14 Docker Desktop for Mac Version 3.1.0 Docker Engine Version 20.10.2 docker-compose version 1.27.4 Prometheus (Docker) version 2.24.1 Alertmanager (Docker) version 0.21.0 Grafana (Docker) version 7.3.7 nginx (Docker) version 1.18.0 やったこと Prometheusを冗長化する Alertmanagerも冗長化する Prometheusを冗長化する Prometheus公式のFAQによると可用性を高めるには\n「同じ設定のものを複数台動かせ. 複数のPrometheusで同じアラートが発火した場合でもAlertmanager側で勝手に重複排除してくれる.(意訳)」1\nとのこと. 単純で良い.\nとりあえずPrometheusを3台立ててnginxで負荷分散(ラウンドロビン)する2ような構成をDocker Composeで作ってみた.\nその他の設定は下記のとおり.\nPrometheus x3 prometheus.yml : 互いを監視するような設定を入れる rules.yml : 任意の設定 Alertmanager alertmanager.yml : 任意の設定 Grafana grafana.ini : 任意の設定(リバースプロキシ対応) datasource.yaml : PrometheusをData Sourceとする設定 nginx default.conf : Prometheusのロードバランシング設定(やらなくても良い) Prometheus, Alertmanager, Grafanaのリバースプロキシ設定は前回やったときとほぼ同じだけど, 今回の目的はPrometheusの冗長化なのでここらへんはやらなくても良い.\nrules.yml, alertmanager.ymlの設定は有効なものなら何でも良いので前回と同じものを使う.\ngrafana.iniも一応前回と同じものを使うけど今回の目的はあくまでPrometheusの冗長化なのでデフォルト設定のままでも良い.\n設定ファイルが準備できたらコンテナを立ち上げる.\n# ディレクトリ構成 $ tree . . ├── deployment │ ├── alertmanager │ │ └── alertmanager.yml │ ├── grafana │ │ ├── datasource.yaml │ │ └── grafana.ini │ ├── nginx │ │ └── default.conf │ └── prometheus │ ├── prometheus.yml │ └── rules.yml └── docker-compose.yml # コンテナ起動 $ docker-compose up -d --force-recreate $ docker-compose ps Name Command State Ports --------------------------------------------------------------------------- alertmanager /bin/alertmanager --config ... Up 9093/tcp grafana /run.sh Up 3000/tcp nginx /docker-entrypoint.sh ngin ... Up 0.0.0.0:80-\u0026gt;80/tcp prometheus-01 /bin/prometheus --config.f ... Up 9090/tcp prometheus-02 /bin/prometheus --config.f ... Up 9090/tcp prometheus-03 /bin/prometheus --config.f ... Up 9090/tcp nginxで80番ポートが開けてあるので, http://localhost/prometheus/を開くとPrometheusが起動していることを確認できる.\n画面上ではどのPrometheusコンテナがレスポンスを返しているのかわからないが, 実際にはnginxにより1台ずつ順番にリクエストを受け付けている(ラウンドロビン). はず. http://localhost/grafanaを開きGrafanaでダッシュボードを作成する.\nData SourceとしてnginxのPrometheus用エンドポイントと念の為それぞれのPrometheus3台のエンドポイントを設定してあるのでそれぞれのData Sourceに対して同じメトリクス(up)のダッシュボードを作ってみる.\n余談だけど今回はnginxロードバランサの下で複数台のPrometheusを動かしているので, GrafanaのData Sourceがnginx上のPrometheusエンドポイントを見ている場合はロードするたびに微妙にグラフ(メトリクス)が変わったりする.\nこの挙動が気持ち悪い場合はnginx上のエンドポイントじゃなくて個別のPrometheusをData Sourceにするほうが良いかもしれない(あとで気づいた).\nこの状態で3台あるPrometheusを一部落としてみる.\n# Prometheusを2台落とす $ docker-compose stop prometheus-01 prometheus-02 $ docker-compose ps Name Command State Ports ---------------------------------------------------------------------------- alertmanager /bin/alertmanager --config ... Up 9093/tcp grafana /run.sh Up 3000/tcp nginx /docker-entrypoint.sh ngin ... Up 0.0.0.0:80-\u0026gt;80/tcp prometheus-01 /bin/prometheus --config.f ... Exit 0 prometheus-02 /bin/prometheus --config.f ... Exit 0 prometheus-03 /bin/prometheus --config.f ... Up 9090/tcp 1台目と2台目のPrometheusを落としたので, 3台目のPrometheusでInstanceDownのアラートが発火する.\nAlertmanagerが反応し, アラートの通知が届く.\n(通知はAlertmanagerからGmail経由で送る設定にしている)\n冗長構成にしたおかげで複数台用意したPrometheusが一部落ちてもメトリクスは継続して収集されていることと,\n相互監視の設定により一部が落ちた場合にも残りのPrometheusでアラートが発火して検知できることが確認できた.\n今度はAlertmanagerで複数のPrometheusから送られた同じアラートが重複排除されることを確かめるため, 3台目のPrometheusだけを落としてみる.\n# Prometheusを復旧させる $ docker-compose start prometheus-01 prometheus-02 # 3台目だけを落とす $ docker-compose stop prometheus-03 $ docker-compose ps Name Command State Ports ---------------------------------------------------------------------------- alertmanager /bin/alertmanager --config ... Up 9093/tcp grafana /run.sh Up 3000/tcp nginx /docker-entrypoint.sh ngin ... Up 0.0.0.0:80-\u0026gt;80/tcp prometheus-01 /bin/prometheus --config.f ... Up 9090/tcp prometheus-02 /bin/prometheus --config.f ... Up 9090/tcp prometheus-03 /bin/prometheus --config.f ... Exit 0 今度は3台目のPrometheusが落ちたので, 1台目と2台目のPrometheusで同じInstanceDownのアラートが発生する.\nただしAlertmanager側で重複が排除されるようになっているので, アラートは1件として扱われて通知も1件のみ届く.\n以上で複数のPrometheusで同じアラートが発火した場合でもAlertmanagerで重複排除されることが確認できた.\nAlertmanagerも冗長化する Prometheusは冗長化できたが, Alertmanagerだって絶対落ちないとは限らないのでこれも冗長化したい.\nただしAlertmanagerの場合は実際に通知を送る機能があるため, Prometheusのように単純に同じものを複数用意するのはあまり良い方法ではなさそう.\n(1つのアラートに対して複数のAlertmanagerがそれぞれ通知を送ってしまう)\nこのため, 複数のAlertmanagerを同時に動かす場合は互いの状況を知るためにメッシュを構成する3.\n難しそうに思えるがAlertmanagerの冗長化機能はあらかじめ有効化されているので, 実行時引数--cluster.peerで同時に動かす対象のホスト名を指定してやれば良い.\n(相互の確認に使うポートは9093でなく9094であることに注意)\nPrometheus x3 prometheus.yml : 監視対象とアラートを送る対象に複数のAlertmanagerを追加する(必須) rules.yml : そのまま Alertmanager x3 alertmanager.yml : そのまま 実行時引数--cluster.peerで他のAlertmanagerを指定(必須) 動作確認用に9093番ポートを疎通可能にしておく(やらなくても良い) Grafana grafana.ini : そのまま datasource.yaml : そのまま nginx default.conf : 複数のAlertmanagerのロードバランシング設定を追加(やらなくても良い) prometheus.ymlでアラートの送信先を指定する際はnginxで負荷分散しているAlertmanagerのエンドポイント(/alertmanager)を指定したくなるが,\n公式FAQによると「PrometheusからはすべてのAlertmanagerにアラートを送れ」とあるので今回はそれに従っている.\n設定ファイルが準備できたら再度コンテナを立ち上げる.\n# ディレクトリ構成 $ tree . . ├── deployment │ ├── alertmanager │ │ └── alertmanager.yml │ ├── grafana │ │ ├── datasource.yaml │ │ └── grafana.ini │ ├── nginx │ │ └── default.conf │ └── prometheus │ ├── prometheus.yml │ └── rules.yml └── docker-compose.yml # コンテナ起動 $ docker-compose up -d --force-recreate --remove-orphans # 今回は動作確認用にlocalhostから直接Alertmanagerにつながるようにしている $ docker-compose ps Name Command State Ports --------------------------------------------------------------------------------- alertmanager-01 /bin/alertmanager --config ... Up 0.0.0.0:8093-\u0026gt;9093/tcp alertmanager-02 /bin/alertmanager --config ... Up 0.0.0.0:8094-\u0026gt;9093/tcp alertmanager-03 /bin/alertmanager --config ... Up 0.0.0.0:8095-\u0026gt;9093/tcp grafana /run.sh Up 3000/tcp nginx /docker-entrypoint.sh ngin ... Up 0.0.0.0:80-\u0026gt;80/tcp prometheus-01 /bin/prometheus --config.f ... Up 9090/tcp prometheus-02 /bin/prometheus --config.f ... Up 9090/tcp prometheus-03 /bin/prometheus --config.f ... Up 9090/tcp まずはそれぞれのAlertmanagerを開いてメッシュが有効になっていることを確認する.\nalertmanager-01 : http://localhost:8093/#/status alertmanager-02 : http://localhost:8094/#/status alertmanager-03 : http://localhost:8095/#/status Cluster Statusを見るとそれぞれのNameを互いにPeersとして認識していることがわかる.\nこの状態で試しに1台目のAlertmanagerでSilence(アラートを一時的に無視する設定)を作成してみる4.\nすると2台目と3台目のAlertmanagerにも同じSilenceが勝手に作成されており, これで3台のAlertmanagerがメッシュとして正しく動作していることがわかる.\n最後にAlertmanagerの動作確認のため, Grafanaを落としてInstanceDownのアラートを発火させてみる.\n# Grafanaを落とす $ docker-compose stop grafana $ docker-compose ps Name Command State Ports ---------------------------------------------------------------------------------- alertmanager-01 /bin/alertmanager --config ... Up 0.0.0.0:8093-\u0026gt;9093/tcp alertmanager-02 /bin/alertmanager --config ... Up 0.0.0.0:8094-\u0026gt;9093/tcp alertmanager-03 /bin/alertmanager --config ... Up 0.0.0.0:8095-\u0026gt;9093/tcp grafana /run.sh Exit 0 nginx /docker-entrypoint.sh ngin ... Up 0.0.0.0:80-\u0026gt;80/tcp prometheus-01 /bin/prometheus --config.f ... Up 9090/tcp prometheus-02 /bin/prometheus --config.f ... Up 9090/tcp prometheus-03 /bin/prometheus --config.f ... Up 9090/tcp 今までと同じくupが0になり, Prometheusでアラートが発火する.\nAlertmanager側でもアラートを受け取っているが,\n先程確認したとおり3台のAlertmanagerはメッシュとして動作しているのでそれぞれが動作して3回通知が送られるということはなく通知は1件のみ届く.\nやったぜ.\nPrometheusだけでなくAlertmanagerも冗長構成で動かすことができた.\nおわり PrometheusとGrafanaを冗長構成で動かしてみた.\n今回はGrafanaの冗長化はしなかったけど, 落ちたところでグラフが見られなくなるくらいしか困ることがないのでやらなくて正解だったかも.\n(やったとしてもnginxの下に複数台ぶらさげるだけになりそう)\n今回はDocker Composeで試してみたけど, 実際にそれぞれ別のサーバーで動かす場合もコンテナ名で名前解決している部分をホスト名に変えてやれば同様の動作になるはず.\nお金と時間に余裕があればVMとかで試してみたい(たぶんやらない).\nおまけ https://prometheus.io/docs/introduction/faq/#can-prometheus-be-made-highly-available\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://nginx.org/en/docs/http/ngx_http_upstream_module.html\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/prometheus/alertmanager#high-availability\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.robustperception.io/high-availability-prometheus-alerting-and-notification\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2021-02-07T00:00:00Z","image":"/post/2021-02-07-prometheus-alertmanager-high-availability/sotochan.jpg","permalink":"/post/2021-02-07-prometheus-alertmanager-high-availability/","title":"PrometheusとAlertmanagerの冗長構成を試す"},{"content":"やったことのまとめ 自作Exporter, Prometheus, Alertmanager, GrafanaをまとめてDocker Composeで動かした nginxコンテナを追加してリバースプロキシした Prometheus用のExporterを自作するときに動作確認の時点でGrafanaのグラフが見られたりAlertmanagerで通知されるメッセージとかが確認できるとメトリクスやラベルの設計がしやすい(気がする)ので,\nそれらが全部入りの環境をDocker Composeで作ってみた.\nその他もろもろの設定ファイル\nつかうもの macOS Mojave 10.14 Docker Desktop for Mac Version 3.1.0 Docker Engine Version 20.10.2 docker-compose version 1.27.4 Prometheus (Docker) version 2.24.1 Alertmanager (Docker) version 0.21.0 Grafana (Docker) version 7.3.7 nginx (Docker) version 1.18.0 自作Exporter 2112番ポートで起動できてDockerコンテナ化できるものなら何でも良い やったこと とりあえず起動する リバースプロキシする とりあえず起動する それぞれDocker imageが公式で提供されているのでそれをそのまま起動すれば良さそう.\n自作Exporterの動作確認をしたときと同様に適当にdocker-compose.ymlを書いてみる.\n重要なのは以下の設定ファイルをそれぞれのコンテナにマウントしていること.\nPrometheus 自作Exporterなどをスクレイプするための設定(prometheus.yml) Docker Composeで起動しているので他のコンテナについてはすべてコンテナ名で名前解決できる アラートルール(rules.yml) 任意のルールで良い Alertmanager アラート通知設定(alertmanager.yml) 任意の設定で良い Grafana 基本設定(grafana.ini) ログイン不要の設定をしておく Data Source設定(datasource.yaml) Prometheusコンテナを指定する Dashboard設定(dashboard.yaml, dashboard.json) dashboard.jsonは後から作っても良い (rules.yml, alertmanager.ymlは何でも良いので前回のものを流用)\nrules.yml\nalertmanager.yml\n(dashboard.jsonはあらかじめ作成済みのものがなければこの後作成する)\nあとはこれらのファイルが以下のように配置されている状態でDocker Composeでコンテナを立ち上げる.\n# ローカルでビルドしない場合はDockerfileとGo関連のファイルは不要 $ tree . . ├── Dockerfile ├── deployment │ ├── alertmanager │ │ └── alertmanager.yml │ ├── grafana │ │ ├── dashboard.json │ │ ├── dashboard.yaml │ │ ├── datasource.yaml │ │ └── grafana.ini │ └── prometheus │ ├── prometheus.yml │ └── rules.yml ├── docker-compose.yml ├── go.mod ├── go.sum └── main.go # コンテナを起動(コードを編集したなどの理由で再度ビルドしたい場合はさらに--buildを付与する) $ docker-compose up -d --force-recreate $ docker-compose ps Name Command State Ports ------------------------------------------------------------------------------ alertmanager /bin/alertmanager --config ... Up 0.0.0.0:9093-\u0026gt;9093/tcp exporter ./app Up 0.0.0.0:2112-\u0026gt;2112/tcp grafana /run.sh Up 0.0.0.0:3000-\u0026gt;3000/tcp prometheus /bin/prometheus --config.f ... Up 0.0.0.0:9090-\u0026gt;9090/tcp あとはそれぞれのエンドポイントで動作確認する.\n自作Exporter http://localhost:2112/metrics Prometheus http://localhost:9090/ Alertmanager http://localhost:9093/ Grafana http://localhost:3000/ dashboard.jsonが未作成の場合はここで動作確認も兼ねてGrafana上でダッシュボード(自作Exporterのメトリクスがグラフ化できれば何でも良い)を作成し, Share➔Export➔Save to fileからJSONをダウンロードして以降はそれを使うようにする.\n設定が正しく効いていれば予めPrometheusがData Sourceとして登録されていて, 全コンテナのメトリクスがスクレイプできていることも確認できる.\nリバースプロキシする このままでもいいんだけどせっかくなのでnginxでリバースプロキシする.\ndocker-compose.ymlにnginxコンテナを追加して, 以前やったときと同様の設定ファイルを作成してマウントする.\n(リバースプロキシしているので, nginx以外のそれぞれのコンテナのポートは閉じておく)\nPrometheus prometheus.yml, rules.yml: そのまま command(CMD)にリバースプロキシ用の設定1を追加 Alertmanager alertmanager.yml: そのまま command(CMD)にリバースプロキシ用の設定を追加 Grafana datasource.yaml, dashboard.yaml, dashboard.json : そのまま grafana.ini リバースプロキシ用の設定2を追加 nginx リバースプロキシ設定(default.conf) リンク用ページ(index.html) 再度コンテナを起動する.\n$ tree . . ├── Dockerfile ├── deployment │ ├── alertmanager │ │ └── alertmanager.yml │ ├── grafana │ │ ├── dashboard.json │ │ ├── dashboard.yaml │ │ ├── datasource.yaml │ │ └── grafana.ini │ ├── nginx │ │ ├── default.conf │ │ └── index.html │ └── prometheus │ ├── prometheus.yml │ └── rules.yml ├── docker-compose.yml ├── go.mod ├── go.sum └── main.go $ docker-compose up -d --force-recreate $ docker-compose ps Name Command State Ports -------------------------------------------------------------------------- alertmanager /bin/alertmanager --config ... Up 9093/tcp exporter ./app Up grafana /run.sh Up 3000/tcp nginx /docker-entrypoint.sh ngin ... Up 0.0.0.0:80-\u0026gt;80/tcp prometheus /bin/prometheus --config.f ... Up 9090/tcp\nここまでの設定に問題がなければそれぞれのパスでコンテナの動作確認ができる.\n自作Exporter http://localhost/exporter/metrics Prometheus http://localhost/prometheus/ Alertmanager http://localhost/alertmanager/ Grafana http://localhost/grafana/ もしくは, http://localhost/でリンクを張ったHTML(index.html)が表示できるのでそこから飛んでも良い.\n最後に一応Alertmanagerの動作確認をする.\nrules.ymlでInstanceDown(upが0になるだけで発火)のアラートルールを設定しているので, 試しに自作Exporterのコンテナを落としてみる.\n$ docker-compose stop exporter $ docker-compose ps Name Command State Ports --------------------------------------------------------------------------- alertmanager /bin/alertmanager --config ... Up 9093/tcp exporter ./app Exit 2 grafana /run.sh Up 3000/tcp nginx /docker-entrypoint.sh ngin ... Up 0.0.0.0:80-\u0026gt;80/tcp prometheus /bin/prometheus --config.f ... Up 9090/tcp それぞれのコンテナでメトリクスの変化に対応した処理が行われていることが確認できる.\nアラートはAlertmanagerからGmail経由で送る設定にしているのでメールが届くことも確認できる.\nやったぜ.\n自作ExporterとPrometheus, Alertmanager, Grafanaを同時に動かして動作確認することができた.\nおわり 終わってみれば大したことはないんだけど, 一回この構成をつくっておくと自作Exporterの開発が楽しくなる(メトリクスがすぐ可視化されアラートの動作確認までできる)のでやってよかったと思う.\nDocker Composeの練習にもなってよかった.\nおまけ https://prometheus.io/docs/guides/basic-auth/#nginx-configuration\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://grafana.com/tutorials/run-grafana-behind-a-proxy/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2021-02-05T00:00:00Z","image":"/post/2021-02-05-run-prometheus-and-grafana-on-docker/sotochan.jpg","permalink":"/post/2021-02-05-run-prometheus-and-grafana-on-docker/","title":"自作Exporter+Prometheus+Alertmanager+GrafanaをまとめてDocker Composeで動かす"},{"content":"まとめ 腹の上にねこ トンネル ねずみ ソファ 腹の上にねこ 俺のお腹の上に乗るのが癖になったらしい.\n腹の上に乗るの完全に癖になってる pic.twitter.com/9bKr8zVZo0\n\u0026mdash; ずみし (@uzimihsr) January 18, 2021 前から俺が寝っ転がってるとたまに乗ってくることはあったんだけど,\nそのタイミングでたくさん撫でたりおやつをあげたりしてたら習慣化した.\nやっぱりねこは良いことがあるとちゃんと覚えるみたい. かしこい.😊\n人の腹をベッドだと思っている pic.twitter.com/8vAcFHhgmK\n\u0026mdash; ずみし (@uzimihsr) January 25, 2021 俺がソファとかベッドに寝っ転がるとそとちゃんが乗ってきて,\nそのまま俺の顔に頭を向けたりおしりを向けたり何回か向きを変えてベストポジションを探す.\nねこのすりすりこうげき pic.twitter.com/eBX2X6bvwL\n\u0026mdash; ずみし (@uzimihsr) January 9, 2021 おしり pic.twitter.com/kLcfzk1JOX\n\u0026mdash; ずみし (@uzimihsr) January 10, 2021 そしてそのまま1時間くらい動かずに撫でられるのがお決まりのパターン.\nこうなってからが長い pic.twitter.com/DL6ReXMZet\n\u0026mdash; ずみし (@uzimihsr) January 15, 2021 そとちゃんが満足するまで俺が勝手に起きることは許されない!😱\nおしりぽんぽんされてゴロゴロ言ってるし気持ちいいはずなのに怖い顔なの難しすぎ pic.twitter.com/G7Me4xKayS\n\u0026mdash; ずみし (@uzimihsr) January 18, 2021 でもねこが腹の上に乗るとやわらかくてあったかくて気持ち良いしめんどくさいことを全部忘れられるのでとても良い.\nトンネル 普段と違うペットショップに行く機会があって, たまたま目についたねこ用トンネルを買ってみた.\nトンネルがうちにきた pic.twitter.com/q5hRboMbim\n\u0026mdash; ずみし (@uzimihsr) January 5, 2021 キャットトンネル　木目柄\nえらく気に入ったらしく, 毎日飽きずに遊んでいる.\nはしゃいでる pic.twitter.com/j3esWctCdf\n\u0026mdash; ずみし (@uzimihsr) January 28, 2021 だいたい前足と頭だけ突っ込んだ状態で待機しているので, あとは横穴か向かいの出口からおもちゃを投げ込んであげると大はしゃぎであそぶ.\nちくわみたいでいいね〜 pic.twitter.com/6rYEBApoaV\n\u0026mdash; ずみし (@uzimihsr) January 5, 2021 おしり pic.twitter.com/cqBzzk8aF7\n\u0026mdash; ずみし (@uzimihsr) January 29, 2021 買ってよかった.\nねずみ そとちゃんお気に入りのねずみのおもちゃだけど, 流石に遊びすぎてボロボロになってきた\u0026hellip;\nねずみの毛がハゲてきた pic.twitter.com/23lSq8Yq1g\n\u0026mdash; ずみし (@uzimihsr) January 21, 2021 非売品だと思ってたので代わりが見つからず困ってたんだけど, 偶然にもトンネルを買ったのと同じお店で遂にそれっぽいおもちゃを見つけた.\nネズミごっこ\n毛の色も同じで, 目鼻耳のパーツの色も同じだから流石に同じものだと信じたい.\n(しっぽは遊んでるうちに切れた)\n\u0026hellip;が, 転がしたときの音が微妙に違うのでまだ遊んだり遊ばなかったりする.🤔\nねずみドリブル pic.twitter.com/c3u5PI29tr\n\u0026mdash; ずみし (@uzimihsr) December 31, 2020 (↑の動画のシャカシャカ鳴ってるねずみは古いやつ, 新しいのはもっとカラカラ鳴る)\nねこのこだわりは難しい\u0026hellip;\nソファ 😭\n😭😭\n😭😭😭\n😭😭😭😭\nその気になればいつでもバリバリしてやるぞという顔 pic.twitter.com/zyeU2lBaDX\n\u0026mdash; ずみし (@uzimihsr) January 27, 2021 おわり 1月はそとちゃんとたくさん遊んであげられて遊ばせていただけてよかった.\n今月みたいに普段と違うペットショップにいけるとおもちゃのレパートリーも広がるので, 出かけるたびに気にかけるようにしたい.\nおまけ ","date":"2021-02-02T00:00:00Z","image":"/post/2021-02-02-sotochan/sotochan.jpg","permalink":"/post/2021-02-02-sotochan/","title":"1月のそとちゃんまとめ(2021)"},{"content":"まとめ promtoolとamtoolを使うとPrometheusの設定とアラートルール, Alertmanagerの設定をそれぞれ反映前にチェックすることができる.\nDocker上でも動かせるのでCIにも組み込めそう.\n# Prometheus設定ファイルのチェック $ promtool check config prometheus.yml # Docker版 $ docker container run --rm -v=\u0026#34;$PWD/prometheus.yml:/prometheus-config/prometheus.yml\u0026#34; --entrypoint=\u0026#34;promtool\u0026#34; prom/prometheus check config /prometheus-config/prometheus.yml # アラートルールファイルのチェック $ promtool check rules rules.yml # Docker版 $ docker container run --rm -v=\u0026#34;$PWD/rules.yml:/prometheus-config/rules.yml\u0026#34; --entrypoint=\u0026#34;promtool\u0026#34; prom/prometheus check rules /prometheus-config/rules.yml # Alertmanager設定ファイルのチェック $ amtool check-config alertmanager.yml # Docker版 $ docker container run --rm -v=\u0026#34;$PWD/alertmanager.yml:/prometheus-config/alertmanager.yml\u0026#34; --entrypoint=\u0026#34;amtool\u0026#34; prom/alertmanager check-config /prometheus-config/alertmanager.yml 環境 macOS Mojave 10.14 Docker Desktop for Mac Version 3.1.0 Docker Engine Version 20.10.2 Prometheus version 2.24.1 promtool version 0.21.0 Alertmanager version 0.21.0 amtool version 0.20.0 もくじ promtoolとamtool Dockerで起動する promtoolとamtool promtoolはPrometheusに付属している設定ファイルやアラートルールファイルの検証ツール1.\nPrometheusがインストールされていればすぐに使うことができる.\namtoolはpromtoolのAlertmanager版といった感じ.\n# prometheus設定ファイル(prometheus.yml)とアラートルールファイル(rules.yml)を準備 $ ls prometheus.yml rules.yml # prometheus.ymlの内容をチェック # prometheus.ymlでルールファイルを参照している場合はその対象もチェックしてくれる $ promtool check config prometheus.yml Checking prometheus.yml SUCCESS: 1 rule files found Checking rules.yml SUCCESS: 1 rules found # rulesだけのチェックも可能 $ promtool check rules rules.yml Checking rules.yml SUCCESS: 1 rules found # alertmanager設定ファイルも同様にチェックできる $ amtool check-config alertmanager.yml Checking \u0026#39;alertmanager.yml\u0026#39; SUCCESS Found: - global config - route - 1 inhibit rules - 1 receivers - 0 templates これを使えばPrometheusやAlertmanagerの設定反映前に構文エラーをチェックすることができて便利.\n試しにわざと間違ったPrometheus設定ファイルとアラートルールファイル, Alertmanager設定ファイルをチェックしてみる.\n# 間違ったprometheus設定ファイル(prometheus.yml)をチェックする $ promtool check config prometheus-error.yml Checking prometheus-error.yml FAILED: parsing YAML file prometheus-error.yml: yaml: unmarshal errors: line 13: field target not found in type struct { Targets []string \u0026#34;yaml:\\\u0026#34;targets\\\u0026#34;\u0026#34;; Labels model.LabelSet \u0026#34;yaml:\\\u0026#34;labels\\\u0026#34;\u0026#34; } # 間違ったアラートルールファイルをチェックする $ promtool check rules rules-error.yml Checking rules-error.yml FAILED: rules-error.yml: group \u0026#34;instance\u0026#34;, rule 0, \u0026#34;InstanceDown\u0026#34;: could not parse expression: 1:4: parse error: could not parse remaining input \u0026#34;= 0\u0026#34;... # 間違ったAlertmanager設定ファイルをチェックする $ amtool check-config alertmanager-error.yml Checking \u0026#39;alertmanager-error.yml\u0026#39; FAILED: yaml: unmarshal errors: line 7: field receiver not found in type config.plain amtool: error: failed to validate 1 file(s) ちゃんとエラー箇所とエラーの内容が表示された.\nべんり.😊\nDockerで起動する 便利なpromtoolとamtoolだけど, PrometheusやAlertmanagerがインストールされていない環境ではなかなか使いづらい.\n(実務でPrometheus/Alertmanagerの設定を直接手でいじることはあんまりなくて, リポジトリ上で設定ファイルを管理してCI/CDすることが多い. と思う.)\nということでDockerを使ってPrometheusがインストールされていない環境でもpromtoolを使えないか試してみる. # Dockerでもできそう $ docker container run --rm --entrypoint=\u0026#34;promtool\u0026#34; prom/prometheus --version promtool, version 2.24.1 (branch: HEAD, revision: e4487274853c587717006eeda8804e597d120340) build user: root@0b5231a0de0f build date: 20210120-00:09:36 go version: go1.15.6 platform: linux/amd64 $ docker container run --rm --entrypoint=\u0026#34;amtool\u0026#34; prom/alertmanager --version amtool, version 0.21.0 (branch: HEAD, revision: 4c6c03ebfe21009c546e4d1e9b92c371d67c021d) build user: root@dee35927357f build date: 20200617-08:54:02 go version: go1.14.4 コマンドは実行可能みたいなので, あとはDockerコンテナ起動時のオプションで設定ファイルをマウントすれば良さそう.\n$ ls alertmanager-error.yml prometheus-error.yml rules-error.yml # Dockerでもできた $ docker container run --rm -v=\u0026#34;$PWD:/prometheus-config\u0026#34; --entrypoint=\u0026#34;promtool\u0026#34; prom/prometheus check config /prometheus-config/prometheus-error.yml Checking /prometheus-config/prometheus-error.yml FAILED: parsing YAML file /prometheus-config/prometheus-error.yml: yaml: unmarshal errors: line 13: field target not found in type struct { Targets []string \u0026#34;yaml:\\\u0026#34;targets\\\u0026#34;\u0026#34;; Labels model.LabelSet \u0026#34;yaml:\\\u0026#34;labels\\\u0026#34;\u0026#34; } $ docker container run --rm -v=\u0026#34;$PWD:/prometheus-config\u0026#34; --entrypoint=\u0026#34;promtool\u0026#34; prom/prometheus check rules /prometheus-config/rules-error.yml Checking /prometheus-config/rules-error.yml FAILED: /prometheus-config/rules-error.yml: 5:13: group \u0026#34;instance\u0026#34;, rule 1, \u0026#34;InstanceDown\u0026#34;: could not parse expression: 1:4: parse error: unexpected \u0026#34;=\u0026#34; $ docker container run --rm -v=\u0026#34;$PWD:/prometheus-config\u0026#34; --entrypoint=\u0026#34;amtool\u0026#34; prom/alertmanager check-config /prometheus-config/alertmanager-error.yml Checking \u0026#39;/prometheus-config/alertmanager-error.yml\u0026#39;amtool: error: failed to validate 1 file(s) FAILED: yaml: unmarshal errors: line 7: field receiver not found in type config.plain # エラー時はちゃんと終了ステータスがnot 0になっている $ echo $? 1 やったぜ.\nPrometheusやAlertmanagerがインストールされていない環境でもDocker上でpromtoolとamtoolを使って設定ファイルの検証ができた.\nおわり promtoolとamtoolを触ってみて, Dockerで起動するところまで試した.\nDocker imageさえあれば起動できるので, あとは細かい設定さえすればGitHub ActionsとかのCIツールで設定ファイルのチェックができると思う.\n今回はpromtoolで設定ファイルのチェックしか試してないけど, 他にもアラートルールのユニットテストなんかもできるみたい2なので余裕があったらそっちも触ってみたい.\nおまけ https://www.robustperception.io/how-to-check-your-prometheus-yml-is-valid\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://prometheus.io/docs/prometheus/latest/configuration/unit_testing_rules/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2021-01-28T00:00:00Z","image":"/post/2021-01-28-promtool-amtool-check-config/sotochan.jpg","permalink":"/post/2021-01-28-promtool-amtool-check-config/","title":"promtool/amtoolでPrometheus/Alertmanagerの設定をチェックする"},{"content":"まとめ Pod終了時のphaseはコンテナのcommandとargsで指定されたメインプロセスの終了ステータスで判定されるので,\ncommandに/bin/sh -cを指定し, argsにコマンドを羅列するような場合は子プロセスの終了ステータスの扱いに気をつけないとエラーが発生しているのに異常終了しないことがある.\nset -eや\u0026amp;\u0026amp;, $?を適宜使い分けるのが大切.\n# falseコマンドで終了ステータス=1が返っているがハンドリングされていないので最後まで実行される $ /bin/sh -c \u0026#39;echo \u0026#34;abc\u0026#34;; false; echo \u0026#34;def\u0026#34;\u0026#39; abc def $ echo $? 0 # set -eで途中0以外の終了ステータスが発生したらそこでプロセスを止める $ /bin/sh -c \u0026#39;set -e; echo \u0026#34;abc\u0026#34;; false; echo \u0026#34;def\u0026#34;\u0026#39; abc $ echo $? 1 # $?で終了ステータスを扱うことでも止められる $ /bin/sh -c \u0026#39;echo \u0026#34;abc\u0026#34;; false; result=$?; if [ $result -ne 0 ]; then exit $result; fi; echo \u0026#34;def\u0026#34;\u0026#39; abc $ echo $? 1 # \u0026amp;\u0026amp;を使って前のコマンドが正常に終了したときだけ次のコマンドを実行させる $ /bin/sh -c \u0026#39;echo \u0026#34;abc\u0026#34; \u0026amp;\u0026amp; false \u0026amp;\u0026amp; echo \u0026#34;def\u0026#34;\u0026#39; abc $ echo $? 1 環境 macOS Mojave 10.14 Docker Desktop for Mac Version 2.5.0.0 Docker version 19.03.13 kind v0.9.0 go1.15.5 darwin/amd64 kubectl Client Version: v1.19.3 Server Version: v1.19.1 もくじ やりがちなケース なんで? 試してみる やりがちなケース 自分もたまにやりがちなんだけど, こんな感じでcommandを/bin/sh -c, argsにshで実行したいコマンドを羅列するようなものを作ることがある.\n一見特に問題ないようにみえるけど, 次のようなものになるとたまに困ることがある.\nこのJobは途中で未定義のyourcoolcmdを呼び出そうとしているので, 途中で失敗する(\u0026ldquo;def\u0026quot;はechoされない).\n\u0026hellip;と思いきや, 実際に動かしてみるとJobは最後まで実行されて正常終了してしまう.\n# JobのPodが正常終了している $ kubectl apply -f job-01.yaml $ kubectl get job example-job-command-not-found NAME COMPLETIONS DURATION AGE example-job-command-not-found 1/1 7s 3m42s $ kubectl get pod -l job-name=example-job-command-not-found NAME READY STATUS RESTARTS AGE example-job-command-not-found-j2dbv 0/1 Completed 0 3m20s # not foundのエラーはちゃんと発生しているのに最後(echo \u0026#34;def\u0026#34;)まで実行されている $ kubectl logs example-job-command-not-found-j2dbv abc /bin/sh: yourcoolcmd: not found def コマンドの結果がなにかおかしかったら異常終了してほしいようなときにこの挙動は少し困ってしまう.\nなんで? 結論から言うと, 原因は先のJob(というかPod)で起動したコンテナのプロセス\n/bin/sh -c \u0026#39;echo \u0026#34;abc\u0026#34;; yourcoolcmd; echo \u0026#34;def\u0026#34;\u0026#39; が異常終了していなかった(終了ステータスが0だった)ため.\n# exitCodeが0(正常終了)になっている $ kubectl get pod example-job-command-not-found-j2dbv -o yaml | yq r - \u0026#34;status\u0026#34; ... containerStatuses: - containerID: containerd://36c7d09a5f262ff2c18bb328f006059c87aafb8414ae293517eca3ec1e75844f image: docker.io/library/busybox:latest imageID: docker.io/library/busybox@sha256:c5439d7db88ab5423999530349d327b04279ad3161d7596d2126dfb5b02bfd1f lastState: {} name: busybox ready: false restartCount: 0 started: false state: terminated: containerID: containerd://36c7d09a5f262ff2c18bb328f006059c87aafb8414ae293517eca3ec1e75844f exitCode: 0 finishedAt: \u0026#34;2021-01-24T07:40:28Z\u0026#34; reason: Completed startedAt: \u0026#34;2021-01-24T07:40:28Z\u0026#34; phase: Succeeded ... まず大前提として, コンテナが終了したときのPodのステータス(phase)は次のどちらかの状態になる1.\nSucceeded Pod内のすべてのコンテナが正常に終了した Failed Pod内のすべてのコンテナが終了し、少なくとも1つのコンテナが異常終了した(コンテナが0以外のステータスで終了したか、システムによって終了された) Failedの条件のコンテナが0以外のステータスで終了したというのが重要.\n先程のPodのコンテナでargsに指定したコマンド\necho \u0026#34;abc\u0026#34; yourcoolcmd echo \u0026#34;def\u0026#34; はそれぞれ/bin/sh -c echo ...というプロセスの子プロセスとして起動される.\n試しにMac上で同じコマンドを実行してみるとよくわかるが, たとえ途中のコマンド(yourcoolcmd)が失敗しても親プロセスが止まらず最後のコマンド(echo \u0026quot;def\u0026quot;)が正常に終了しているので/bin/sh -c echo ...の終了ステータスは0(正常終了)になる.\n# 途中の子プロセスが異常終了しても親プロセスが最後まで実行されている $ /bin/sh -c \u0026#39;echo \u0026#34;abc\u0026#34;; yourcoolcmd; echo \u0026#34;def\u0026#34;\u0026#39; abc /bin/sh: yourcoolcmd: command not found def $ echo $? 0 したがって, Podのcommandとargsで指定されたプロセスの終了ステータスが0になってしまうので,\nPodのphaseがSucceededとなりJobも成功扱いになっている(たぶん).\nこれを防ぐためには, それぞれの終了ステータス($?)を観てエラーハンドリングするか, set -eもしくは\u0026amp;\u0026amp;を使うのが良い.\n# 終了ステータス($?)を観てエラーハンドリングする $ /bin/sh -c \u0026#39;echo \u0026#34;abc\u0026#34;; yourcoolcmd; if [ $? -ne 0 ]; then exit 1; fi; echo \u0026#34;def\u0026#34;\u0026#39; abc /bin/sh: yourcoolcmd: command not found $ echo $? 1 # set -eを使う # 途中で0以外の終了ステータスが発生したときにそこで終了する $ /bin/sh -c \u0026#39;set -e; echo \u0026#34;abc\u0026#34;; yourcoolcmd; echo \u0026#34;def\u0026#34;\u0026#39; abc /bin/sh: yourcoolcmd: command not found $ echo $? 127 # \u0026amp;\u0026amp;を使う # 前のコマンドが正常終了しない場合は次のコマンドが実行されない $ /bin/sh -c \u0026#39;echo \u0026#34;abc\u0026#34; \u0026amp;\u0026amp; yourcoolcmd \u0026amp;\u0026amp; echo \u0026#34;def\u0026#34;\u0026#39; abc /bin/sh: yourcoolcmd: command not found $ echo $? 127 試してみる 原因がなんとなくわかったので,\nさっきのJobをちゃんと0以外の終了ステータスで終わるようにした.\n(バッチ処理を想定して\u0026amp;\u0026amp;を使っているが, もちろん用途に応じて適宜set -eとか$?を使い分けるべき.)\n# JobがBackoffLimitExceededで終了している $ kubectl apply -f job-02.yaml $ kubectl get job example-job-command-not-found-2 NAME COMPLETIONS DURATION AGE example-job-command-not-found-2 0/1 2m14s 2m14s $ kubectl get job example-job-command-not-found-2 -o yaml | yq r - \u0026#34;status\u0026#34; conditions: - lastProbeTime: \u0026#34;2021-01-24T09:10:53Z\u0026#34; lastTransitionTime: \u0026#34;2021-01-24T09:10:53Z\u0026#34; message: Job has reached the specified backoff limit reason: BackoffLimitExceeded status: \u0026#34;True\u0026#34; type: Failed failed: 2 startTime: \u0026#34;2021-01-24T09:10:40Z\u0026#34; # Podのコンテナがちゃんと0以外の終了ステータス(exitCode)で終了している $ kubectl get pod -l job-name=example-job-command-not-found-2 NAME READY STATUS RESTARTS AGE example-job-command-not-found-2-bq4bl 0/1 Error 0 3m16s example-job-command-not-found-2-jkdcw 0/1 Error 0 3m13s $ kubectl get pod example-job-command-not-found-2-bq4bl -o yaml | yq r - \u0026#34;status\u0026#34; ... containerStatuses: - containerID: containerd://09caecce3d813e48cacd53f90eefd5e4f18b7565af7c9351c7fef47dbe397834 image: docker.io/library/busybox:latest imageID: docker.io/library/busybox@sha256:c5439d7db88ab5423999530349d327b04279ad3161d7596d2126dfb5b02bfd1f lastState: {} name: busybox ready: false restartCount: 0 started: false state: terminated: containerID: containerd://09caecce3d813e48cacd53f90eefd5e4f18b7565af7c9351c7fef47dbe397834 exitCode: 127 finishedAt: \u0026#34;2021-01-24T09:10:42Z\u0026#34; reason: Error startedAt: \u0026#34;2021-01-24T09:10:42Z\u0026#34; phase: Failed ... $ kubectl logs example-job-command-not-found-2-bq4bl abc /bin/sh: yourcoolcmd: not found ちゃんとPodの終了ステータスが0以外となり, Jobが失敗扱いになったことを確認できた.\nおわり Kubernetesというよりはシェルスクリプトの基本のおさらいになってしまったが, たまにやってしまうので戒めとして書いた.\n終了ステータスの扱いは大切.\nおまけ https://kubernetes.io/ja/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2021-01-24T17:58:02+09:00","image":"/post/2021-01-24-sh-status/sotochan.jpg","permalink":"/post/2021-01-24-sh-status/","title":"Kubernetes Podのcommandに/bin/shを使うときは終了ステータスの扱いに気をつけようという話"},{"content":"まとめ パソコン絶対邪魔するねこ 成長 クリスマス パソコン絶対邪魔するねこ 今まであんまり邪魔してこなかったのに,\n俺が机で仕事をするようになってからやたらとパソコンの近くで遊ぶようになった.\nねこちゃんがキーボードに乗れないようにMacBook用のスタンド導入したんだけど…😭 pic.twitter.com/RApI7y92MD\n\u0026mdash; ずみし (@uzimihsr) December 8, 2020 一度こうなるとそとちゃんが満足するまで撫でないといけないので, とても困る(うれしい).\nかまってタイム pic.twitter.com/JWFFwY9U3f\n\u0026mdash; ずみし (@uzimihsr) December 17, 2020 また, 今月導入した在宅勤務用のイスが気に入ったらしくて気を抜くとしょっちゅう占領していた.\nパソコンの次は椅子\n仕事妨害の手段が日々効率化されていく pic.twitter.com/eB9v0SSaHY\n\u0026mdash; ずみし (@uzimihsr) December 9, 2020 そとちゃんは変なところで肝が据わっているので, ちょっと動かしたくらいじゃぜんぜん降りようとしない\u0026hellip;\n回した pic.twitter.com/xTI9NqPx57\n\u0026mdash; ずみし (@uzimihsr) December 10, 2020 俺が仕事してなくても夜はイスで寝てたりするので, 単純に座り心地が気に入ったのかも?\nねた pic.twitter.com/9I0hipHXoF\n\u0026mdash; ずみし (@uzimihsr) December 14, 2020 成長 以前はねずみのおもちゃを失くすとそのままやる気をなくして寝てたりしたんだけど,\n最近は失くしたらすぐに鳴いて教えてくれるようになった. えらい😊\n「おもちゃが取れないところに入っちゃったからとってください」の顔 pic.twitter.com/SYw2YPjRau\n\u0026mdash; ずみし (@uzimihsr) December 22, 2020 でもそれ以前におもちゃを失くさずに遊べるようになると, もっとえらいなあ\u0026hellip;😅\n(そとちゃんはなぜか台所でねずみをドリブルするのが好きなので, 冷蔵庫の下によくシュートして取れなくなっちゃう)\nねこおちてた pic.twitter.com/uehluq1cmd\n\u0026mdash; ずみし (@uzimihsr) December 22, 2020 クリスマス 今年もそとちゃんがかわいいサンタさんになってくれた😊\nサンタねこ pic.twitter.com/cK1ZWEUct4\n\u0026mdash; ずみし (@uzimihsr) December 24, 2020 \u0026hellip;しかし服が結構動きづらくてそとちゃんが不機嫌だったので, サンタさんは数分で終了\u0026hellip;\n詫びちゅーるでなんとか許してもらった.\nクリスマスちゅーる pic.twitter.com/lP8DkHjiJy\n\u0026mdash; ずみし (@uzimihsr) December 24, 2020 来年は腕を通さないマントタイプの服にしよう\u0026hellip;\nクリスマスプレゼントにはニャンコロビーをあげた🎁\n今年もいい子にしてたねこにクリスマスプレゼント pic.twitter.com/vzOqXSmFjR\n\u0026mdash; ずみし (@uzimihsr) December 24, 2020 喜んでくれてよかった🎉\n(この後1日で飽きちゃったのは秘密\u0026hellip;😭)\nおわり 今月もそとちゃんはよく遊んでよく食べて良い子だった.\n世間は大変な1年だったけどそとちゃんは今年も特に大きな病気(誤飲騒動を除く)はなかったし,\n俺が在宅勤務になったのもあって去年よりも一緒に遊べる時間が増えて楽しかった.\n来年もそとちゃんが幸せに暮らせるようにしたい.\nおまけ ","date":"2020-12-30T00:00:00Z","image":"/post/2020-12-30-sotochan/sotochan.jpg","permalink":"/post/2020-12-30-sotochan/","title":"12月のそとちゃんまとめ(2020)"},{"content":"やったことのまとめ Hugoを更新した HugoのThemeをBeautiful HugoからHugo Theme Stackに乗り換えた つかうもの macOS Mojave 10.14 Hugo v0.79.0 今回v0.56.3からv0.79.0に更新した Hugo Theme Stack やったこと Hugoの更新 なんかおかしい Themeの変更 Hugoの更新 使っているHugoのバージョンが結構古くなっていたので, 最新版にする.\n自分の場合はbrewでインストールしているので, まずはbrew upgrade\u0026hellip;\nしようとしたら怒られた.\nなんかGitHubとbrewの間でいろいろあって, shallow cloneをしないように変更があったらしい1.\n# どうして... $ brew update Error: homebrew-core is a shallow clone. homebrew-cask is a shallow clone. To `brew update`, first run: git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core fetch --unshallow git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-cask fetch --unshallow This restriction has been made on GitHub\u0026#39;s request because updating shallow clones is an extremely expensive operation due to the tree layout and traffic of Homebrew/homebrew-core and Homebrew/homebrew-cask. We don\u0026#39;t do this for you automatically to avoid repeatedly performing an expensive unshallow operation in CI systems (which should instead be fixed to not use shallow clones). Sorry for the inconvenience! # 言われたとおりにする $ git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core fetch --unshallow $ git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-cask fetch --unshallow # できた $ brew update Updated 2 taps (homebrew/core and homebrew/cask). 気を取り直してHugoを更新する.\n# 更新前の状態 $ hugo version Hugo Static Site Generator v0.56.3/extended darwin/amd64 BuildDate: unknown # hugoを更新 $ brew upgrade hugo # 最新版になった $ hugo version Hugo Static Site Generator v0.79.0/extended darwin/amd64 BuildDate: unknown できた.\nなんかおかしい Hugoの更新後にブログの状態をローカルで確認するためにhugo serverを実行したらなんかやたらと警告が出た.\n# ページを立ち上げてみる... $ cd blog $ hugo server -D ... WARN 2020/12/17 23:55:53 Failed to get translated string for language \u0026#34;en\u0026#34; and ID \u0026#34;postedOnDate\u0026#34;: template: :1:13: executing \u0026#34;\u0026#34; at \u0026lt;.Count\u0026gt;: can\u0026#39;t evaluate field Count in type string ... なんかテンプレートに渡されるべきフィールドが変わっちゃってる?っぽくて直し方がよくわからなかった(無能)のと,\n今まで使ってたBeautiful Hugoの開発が2019年の11月で止まっちゃってた2のもあって, これを機にThemeを変えることにした.\n(自分で作ろうとしないあたりがダメ)\nThemeの変更 適当にThemeの一覧3を漁ってたら良さそうなのがあった.\nHugo Theme Stack4\nシンプルなデザインでいい感じ.\nこのままtheme配下にsubmodule addしてもいいんだけど,\nどうしても細かい変更がしたいことがあったりするのでforkして使わせていただく\u0026hellip;\n(ライセンスなどはいじらないこと!)\n# themeを追加する $ git submodule add https://github.com/uzimihsr/hugo-theme-stack.git themes/hugo-theme-stack # サンプルサイトの内容をコピーする $ cp -r ./themes/hugo-theme-stack/exampleSite/* ./ あとはconfig.tomlの内容をconfig.yamlに書き直したり,\n今までの記事の内容をcontent/post配下に移動したり画像ファイルの場所を変えたりしてお引越しは完了.\nあまりにも泥臭い作業だったので詳細は省くけど,\n今までとはpost配下の構造が違う(記事タイトルのディレクトリ配下にindex.mdと画像ファイルを配置する方式)ので元のブログ用ディレクトリの内容をいい感じにコピーするスクリプトを書いたりもした.\n(ほんとにしょぼかったので割愛)\nとりあえず元の記事は全部引っ越せたのでおしまい.\nおわり ブログのThemeを変えてみた.\n前のデザインもシンプルで良かったけど, 新しいThemeもかなりいい感じ.\nおまけ https://github.com/Homebrew/brew/pull/9383\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/halogenica/beautifulhugo\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://themes.gohugo.io/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/CaiJimmy/hugo-theme-stack\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2020-12-23T22:30:18+09:00","image":"/post/2020-12-23-hugo-theme/sotochan.jpg","permalink":"/post/2020-12-23-hugo-theme/","title":"Hugoを新しくしてThemeを変えた"},{"content":"めっちゃべんり kindで作成したKubernetesクラスタを使ってHelmクイックスタートの内容を試した.\nやったことのまとめ HelmのCLIをMacにインストールした サンプルChartをKubernetesクラスタにインストールした # Chartリポジトリの追加 $ helm repo add \u0026lt;リポジトリ名\u0026gt; \u0026lt;リポジトリURL\u0026gt; # Chartリポジトリの更新 $ helm repo update # 利用可能なChartの一覧を確認 $ helm search repo \u0026lt;キーワード\u0026gt; # Chartの簡易情報 $ helm show chart \u0026lt;Chart名\u0026gt; # Chartのインストール $ helm install \u0026lt;Chart名\u0026gt; --generate-name # リリースの確認 $ helm ls # Chartのアンインストール $ helm uninstall \u0026lt;リリース名\u0026gt; つかうもの macOS Mojave 10.14 Docker Desktop for Mac Version 2.5.0.0 Docker version 19.03.13 インストール済み kind v0.9.0 go1.15.5 darwin/amd64 インストール済み Kubernetes v1.19.1 kindで作成 kubectl Client Version: v1.19.3 インストール済み Helm v3.4.2+g23dd3af 今回インストールする やったこと Helmについて Kubernetesクラスタの準備 MacにHelmをインストール Chartのインストール Chartのアンインストール Helmについて Helm1はKubernetes用のパッケージマネージャー.\nアプリケーションを動かすために必要な各種Kubernetesリソース(Pod, Service, ConfigMapなど)の定義や依存関係, 設定をまとめたChartを使うことでアプリケーションのデプロイを簡単にしてくれる. らしい.\n(Ubuntuでいうapt, CentOSでいうyumみたいなもの)\n以前はChartを解釈するためのTillerというHelm専用のコンポーネントをKubernetesクラスタにデプロイする必要があって面倒そうだったのでちょっと敬遠してたんだけど,\nいつの間にか(Helm v3.0以降)Tillerが不要になった2らしいので試してみた.\nKubernetesクラスタの準備 まずはHelmでアプリを動かすためのKubernetesクラスタをkindで作成する.\n今回は一応Ingressが使えるよう設定したけど, それも省けばかなりかんたんにクラスタを作成できる.\nやっぱりkindはすごい.\n# kindでクラスタを作成 $ cat \u0026lt;\u0026lt;EOF | kind create cluster --name helm-practice --config=- kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane kubeadmConfigPatches: - | kind: InitConfiguration nodeRegistration: kubeletExtraArgs: node-labels: \u0026#34;ingress-ready=true\u0026#34; extraPortMappings: - containerPort: 80 hostPort: 80 protocol: TCP - containerPort: 443 hostPort: 443 protocol: TCP EOF Creating cluster \u0026#34;helm-practice\u0026#34; ... ✓ Ensuring node image (kindest/node:v1.19.1) 🖼 ✓ Preparing nodes 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 Set kubectl context to \u0026#34;kind-helm-practice\u0026#34; You can now use your cluster with: kubectl cluster-info --context kind-helm-practice Thanks for using kind! 😊 # contextは自動で切り替わるけど念の為明示的に変えておく $ kubectl config use-context kind-helm-practice Switched to context \u0026#34;kind-helm-practice\u0026#34;. # Ingress Controllerのデプロイ $ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/provider/kind/deploy.yaml namespace/ingress-nginx created serviceaccount/ingress-nginx created configmap/ingress-nginx-controller created clusterrole.rbac.authorization.k8s.io/ingress-nginx created clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created role.rbac.authorization.k8s.io/ingress-nginx created rolebinding.rbac.authorization.k8s.io/ingress-nginx created service/ingress-nginx-controller-admission created service/ingress-nginx-controller created deployment.apps/ingress-nginx-controller created validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created serviceaccount/ingress-nginx-admission created clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created role.rbac.authorization.k8s.io/ingress-nginx-admission created rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created job.batch/ingress-nginx-admission-create created job.batch/ingress-nginx-admission-patch created $ kubectl wait --namespace ingress-nginx \\ --for=condition=ready pod \\ --selector=app.kubernetes.io/component=controller \\ --timeout=90s pod/ingress-nginx-controller-6df69bd4f7-brc52 condition met MacにHelmをインストール 以降はクイックスタート3の内容をなぞっていく.\nHelmを使うにはまずChartをKubernetesクラスタに投下するためのCLIが必要になる.\n\u0026hellip;が, Macの場合はbrewで入るのでらくちん.\n# helmをインストール $ brew install helm $ which helm /usr/local/bin/helm # 動作確認 $ helm version --short v3.4.2+g23dd3af # completionを効かせておくと楽 $ source \u0026lt;(helm completion zsh) CLIがインストールできたら, Chartを取得するためのリポジトリを追加する.\n# Chartリポジトリの追加 $ helm repo add stable https://charts.helm.sh/stable \u0026#34;stable\u0026#34; has been added to your repositories # Chartリポジトリを更新する $ helm repo update Hang tight while we grab the latest from your chart repositories... ...Successfully got an update from the \u0026#34;stable\u0026#34; chart repository Update Complete. ⎈Happy Helming!⎈ これでHelmの準備はOK.\nChartのインストール いよいよChartをKubernetesクラスタにインストールする.\n今回は例としてMySQLのChartを使う.\nHelmのCLIはkubectlのcontextを参照してKubernetes APIへ操作を行うので,\ncontextが所望の設定になっているか注意する.\n# Chartをインストールする前の状態 $ kubectl get all NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.96.0.1 \u0026lt;none\u0026gt; 443/TCP 10m # Chart(stable/mysql)の簡易情報を確認 $ helm show chart stable/mysql apiVersion: v1 appVersion: 5.7.30 deprecated: true description: DEPRECATED - Fast, reliable, scalable, and easy to use open-source relational database system. home: https://www.mysql.com/ icon: https://www.mysql.com/common/logos/logo-mysql-170x115.png keywords: - mysql - database - sql name: mysql sources: - https://github.com/kubernetes/charts - https://github.com/docker-library/mysql version: 1.6.9 # Chartをインストール $ helm install stable/mysql --generate-name WARNING: This chart is deprecated NAME: mysql-1608125461 LAST DEPLOYED: Wed Dec 16 22:31:04 2020 NAMESPACE: default STATUS: deployed REVISION: 1 NOTES: MySQL can be accessed via port 3306 on the following DNS name from within your cluster: mysql-1608125461.default.svc.cluster.local To get your root password run: MYSQL_ROOT_PASSWORD=$(kubectl get secret --namespace default mysql-1608125461 -o jsonpath=\u0026#34;{.data.mysql-root-password}\u0026#34; | base64 --decode; echo) To connect to your database: 1. Run an Ubuntu pod that you can use as a client: kubectl run -i --tty ubuntu --image=ubuntu:16.04 --restart=Never -- bash -il 2. Install the mysql client: $ apt-get update \u0026amp;\u0026amp; apt-get install mysql-client -y 3. Connect using the mysql cli, then provide your password: $ mysql -h mysql-1608125461 -p To connect to your database directly from outside the K8s cluster: MYSQL_HOST=127.0.0.1 MYSQL_PORT=3306 # Execute the following command to route the connection: kubectl port-forward svc/mysql-1608125461 3306 mysql -h ${MYSQL_HOST} -P${MYSQL_PORT} -u root -p${MYSQL_ROOT_PASSWORD} # DeploymentとServiceが作成されている $ kubectl get all NAME READY STATUS RESTARTS AGE pod/mysql-1608125461-bfdcccddb-h97b8 1/1 Running 0 66s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.96.0.1 \u0026lt;none\u0026gt; 443/TCP 11m service/mysql-1608125461 ClusterIP 10.96.6.222 \u0026lt;none\u0026gt; 3306/TCP 66s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/mysql-1608125461 1/1 1 1 66s NAME DESIRED CURRENT READY AGE replicaset.apps/mysql-1608125461-bfdcccddb 1 1 1 66s # Helmでデプロイされたリリースの確認 $ helm ls NAME NAMESPACE\tREVISION\tUPDATED STATUS CHART APP VERSION mysql-1608125461\tdefault 1 2020-12-16 22:31:04.174492 +0900 JST\tdeployed\tmysql-1.6.9\t5.7.30 MySQLのDeploymentとそれに対応したServiceが作成されている.\nあとはChartインストール時のメッセージに従って動作確認してみる.\n# Macからクラスタ内のServiceにポート転送する(別のシェルで実行) $ kubectl port-forward svc/mysql-1608125461 3306 Forwarding from 127.0.0.1:3306 -\u0026gt; 3306 Forwarding from [::1]:3306 -\u0026gt; 3306 # Chartで自動生成されたSecretからDBのパスワードを取得 $ MYSQL_ROOT_PASSWORD=$(kubectl get secret --namespace default mysql-1608125461 -o jsonpath=\u0026#34;{.data.mysql-root-password}\u0026#34; | base64 --decode; echo) # クラスタ外(Mac)からクラスタ内のMySQLに接続 $ MYSQL_HOST=127.0.0.1 $ MYSQL_PORT=3306 $ mysql -h ${MYSQL_HOST} -P${MYSQL_PORT} -u root -p${MYSQL_ROOT_PASSWORD} mysql: [Warning] Using a password on the command line interface can be insecure. Welcome to the MySQL monitor. Commands end with ; or \\g. Your MySQL connection id is 167 Server version: 5.7.30 MySQL Community Server (GPL) Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type \u0026#39;help;\u0026#39; or \u0026#39;\\h\u0026#39; for help. Type \u0026#39;\\c\u0026#39; to clear the current input statement. mysql\u0026gt; SHOW DATABASES; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | +--------------------+ 4 rows in set (0.01 sec) mysql\u0026gt; ^DBye ChartでインストールしたMySQLがちゃんと動いていることを確認できた.\nやったぜ.\nChartのアンインストール 最後にChartをアンインストールしてみる.\nこれもコマンド1発.\n# Chartをアンインストール $ helm uninstall mysql-1608125461 release \u0026#34;mysql-1608125461\u0026#34; uninstalled # リリースが消えている $ helm ls NAME\tNAMESPACE\tREVISION\tUPDATED\tSTATUS\tCHART\tAPP VERSION # KubernetesもChartをインストールする前の状態に戻っている $ kubectl get all NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.96.0.1 \u0026lt;none\u0026gt; 443/TCP 33m あっさり消えた.\nおわり Helmを使ってChartをKubernetesにインストールしてみた.\nなんとYAMLを1度も書かずに, CentOSでyum installするような感覚でKubernetes上でアプリを動かせてしまった. すごい.\nどうして今まで使ってこなかったんだろうというくらい便利.\nhelmは神.\nおまけ https://helm.sh/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://helm.sh/docs/faq/#changes-since-helm-2\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://helm.sh/docs/intro/quickstart/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2020-12-17T12:10:24+09:00","image":"/post/2020-12-17-helm-on-kind/sotochan.jpg","permalink":"/post/2020-12-17-helm-on-kind/","title":"kindで作ったKubernetesクラスタでHelmを試した"},{"content":"コンテナのログファイルが見たい Docker Desktop for MacでDockerのホストOSに入ってコンテナのログファイルが確認したかったんだけど詰まったのでメモ.\nまとめ 特権コンテナでnsenterを実行するのがかんたん.\n# 特権コンテナを実行してホストOSに入る $ docker run -it --rm --privileged --pid=host justincormack/nsenter1 環境 macOS Mojave 10.14 Docker Desktop for Mac Version 2.5.0.0 Docker version 19.03.13 やりかた Macにはない? Linux上でDockerコンテナを実行したときのログファイルはホストOSの/var/lib/docker/containers/\u0026lt;ContainerID\u0026gt;に吐き出されるんだけど,\nDocker Desktop for Macで動かしたコンテナのログを見ようとしてもそんなディレクトリがそもそもmacOS上に存在しない\u0026hellip;\n# hellp-worldの実行 $ docker container run hello-world Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the \u0026#34;hello-world\u0026#34; image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/ # コンテナIDの確認 $ docker container ls -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f9a220a39165 hello-world \u0026#34;/hello\u0026#34; 17 seconds ago Exited (0) 16 seconds ago busy_matsumoto $ docker container inspect f9a220a39165 -f {{.Id}} f9a220a391654fd66c8094a2965da6e1255ffb97c6db047d0aef1985d2e49659 # ディレクトリがない $ ls /var/lib/docker/containers/f9a220a391654fd66c8094a2965da6e1255ffb97c6db047d0aef1985d2e49659 ls: /var/lib/docker/containers/f9a220a391654fd66c8094a2965da6e1255ffb97c6db047d0aef1985d2e49659: No such file or directory $ ls /var/lib/docker/containers ls: /var/lib/docker/containers: No such file or directory これはDocker Desktop for MacのDockerがmacOS上で直接動作しているわけではなく,\nmacOS上に構築されたHyperKit VMの上で動作している1ため.\nVMに入る じゃあHyperKit VMに入ればいいじゃんという話.\nちょっと調べると\n~/Library/Containers/com.docker.docker/Data/vms/0/ttyをscreenで呼び出す 特権コンテナを立ち上げてnsenterでホストOSに入る といった方法2が出てきた.\n\u0026hellip;が, 自分の環境だとscreenを実行してもCannot exec '/Users/uzimihsr/Library/Containers/com.docker.docker/Data/vms/0/tty': No such file or directoryと表示されて落ちてしまった.\n# どうして... $ screen ~/Library/Containers/com.docker.docker/Data/vms/0/tty [screen is terminating] # そもそもttyコマンドが見当たらない $ ls ~/Library/Containers/com.docker.docker/Data/vms/0/tty ls: /Users/uzimihsr/Library/Containers/com.docker.docker/Data/vms/0/tty: No such file or directory なんかIssueに似た症状のコメント3も上がってるので, 自分だけじゃないはず\u0026hellip;🤔\n回避方法もよくわかんなかったのでscreenを使う方法は諦めて,\n特権コンテナ4を使う方法を試してみる.\n# 特権コンテナを使ってホストOS(VM)に入る $ docker run -it --rm --privileged --pid=host justincormack/nsenter1 / # hostname docker-desktop / # uname -a Linux docker-desktop 5.4.39-linuxkit #1 SMP Fri May 8 23:03:06 UTC 2020 x86_64 Linux VMに入れたっぽい\u0026hellip;!\n目的のコンテナログも表示できる.\n## 以下すべて特権コンテナ(ホストOS)内で実行 # コンテナのログファイルを探す $ ls /var/lib/docker/containers/f9a220a391654fd66c8094a2965da6e1255ffb97c6db047d0aef1985d2e49659 checkpoints hosts config.v2.json mounts f9a220a391654fd66c8094a2965da6e1255ffb97c6db047d0aef1985d2e49659-json.log resolv.conf hostconfig.json resolv.conf.hash hostname # ログファイルの表示 $ cat /var/lib/docker/containers/f9a220a391654fd66c8094a2965da6e1255ffb97c6db047d0aef1985d2e49659/f9a220a391654fd66c8094a2965da6e1255ffb97c6db047d0aef1985d2e49659-json.log {\u0026#34;log\u0026#34;:\u0026#34;\\n\u0026#34;,\u0026#34;stream\u0026#34;:\u0026#34;stdout\u0026#34;,\u0026#34;time\u0026#34;:\u0026#34;2020-12-15T12:25:05.3727159Z\u0026#34;} {\u0026#34;log\u0026#34;:\u0026#34;Hello from Docker!\\n\u0026#34;,\u0026#34;stream\u0026#34;:\u0026#34;stdout\u0026#34;,\u0026#34;time\u0026#34;:\u0026#34;2020-12-15T12:25:05.3727757Z\u0026#34;} {\u0026#34;log\u0026#34;:\u0026#34;This message shows that your installation appears to be working correctly.\\n\u0026#34;,\u0026#34;stream\u0026#34;:\u0026#34;stdout\u0026#34;,\u0026#34;time\u0026#34;:\u0026#34;2020-12-15T12:25:05.372871Z\u0026#34;} {\u0026#34;log\u0026#34;:\u0026#34;\\n\u0026#34;,\u0026#34;stream\u0026#34;:\u0026#34;stdout\u0026#34;,\u0026#34;time\u0026#34;:\u0026#34;2020-12-15T12:25:05.3728948Z\u0026#34;} {\u0026#34;log\u0026#34;:\u0026#34;To generate this message, Docker took the following steps:\\n\u0026#34;,\u0026#34;stream\u0026#34;:\u0026#34;stdout\u0026#34;,\u0026#34;time\u0026#34;:\u0026#34;2020-12-15T12:25:05.3729341Z\u0026#34;} {\u0026#34;log\u0026#34;:\u0026#34; 1. The Docker client contacted the Docker daemon.\\n\u0026#34;,\u0026#34;stream\u0026#34;:\u0026#34;stdout\u0026#34;,\u0026#34;time\u0026#34;:\u0026#34;2020-12-15T12:25:05.3729571Z\u0026#34;} {\u0026#34;log\u0026#34;:\u0026#34; 2. The Docker daemon pulled the \\\u0026#34;hello-world\\\u0026#34; image from the Docker Hub.\\n\u0026#34;,\u0026#34;stream\u0026#34;:\u0026#34;stdout\u0026#34;,\u0026#34;time\u0026#34;:\u0026#34;2020-12-15T12:25:05.3729785Z\u0026#34;} {\u0026#34;log\u0026#34;:\u0026#34; (amd64)\\n\u0026#34;,\u0026#34;stream\u0026#34;:\u0026#34;stdout\u0026#34;,\u0026#34;time\u0026#34;:\u0026#34;2020-12-15T12:25:05.372997Z\u0026#34;} {\u0026#34;log\u0026#34;:\u0026#34; 3. The Docker daemon created a new container from that image which runs the\\n\u0026#34;,\u0026#34;stream\u0026#34;:\u0026#34;stdout\u0026#34;,\u0026#34;time\u0026#34;:\u0026#34;2020-12-15T12:25:05.3730192Z\u0026#34;} {\u0026#34;log\u0026#34;:\u0026#34; executable that produces the output you are currently reading.\\n\u0026#34;,\u0026#34;stream\u0026#34;:\u0026#34;stdout\u0026#34;,\u0026#34;time\u0026#34;:\u0026#34;2020-12-15T12:25:05.3731413Z\u0026#34;} {\u0026#34;log\u0026#34;:\u0026#34; 4. The Docker daemon streamed that output to the Docker client, which sent it\\n\u0026#34;,\u0026#34;stream\u0026#34;:\u0026#34;stdout\u0026#34;,\u0026#34;time\u0026#34;:\u0026#34;2020-12-15T12:25:05.3731663Z\u0026#34;} {\u0026#34;log\u0026#34;:\u0026#34; to your terminal.\\n\u0026#34;,\u0026#34;stream\u0026#34;:\u0026#34;stdout\u0026#34;,\u0026#34;time\u0026#34;:\u0026#34;2020-12-15T12:25:05.3732665Z\u0026#34;} {\u0026#34;log\u0026#34;:\u0026#34;\\n\u0026#34;,\u0026#34;stream\u0026#34;:\u0026#34;stdout\u0026#34;,\u0026#34;time\u0026#34;:\u0026#34;2020-12-15T12:25:05.3732845Z\u0026#34;} {\u0026#34;log\u0026#34;:\u0026#34;To try something more ambitious, you can run an Ubuntu container with:\\n\u0026#34;,\u0026#34;stream\u0026#34;:\u0026#34;stdout\u0026#34;,\u0026#34;time\u0026#34;:\u0026#34;2020-12-15T12:25:05.3733071Z\u0026#34;} {\u0026#34;log\u0026#34;:\u0026#34; $ docker run -it ubuntu bash\\n\u0026#34;,\u0026#34;stream\u0026#34;:\u0026#34;stdout\u0026#34;,\u0026#34;time\u0026#34;:\u0026#34;2020-12-15T12:25:05.3733248Z\u0026#34;} {\u0026#34;log\u0026#34;:\u0026#34;\\n\u0026#34;,\u0026#34;stream\u0026#34;:\u0026#34;stdout\u0026#34;,\u0026#34;time\u0026#34;:\u0026#34;2020-12-15T12:25:05.373345Z\u0026#34;} {\u0026#34;log\u0026#34;:\u0026#34;Share images, automate workflows, and more with a free Docker ID:\\n\u0026#34;,\u0026#34;stream\u0026#34;:\u0026#34;stdout\u0026#34;,\u0026#34;time\u0026#34;:\u0026#34;2020-12-15T12:25:05.3733674Z\u0026#34;} {\u0026#34;log\u0026#34;:\u0026#34; https://hub.docker.com/\\n\u0026#34;,\u0026#34;stream\u0026#34;:\u0026#34;stdout\u0026#34;,\u0026#34;time\u0026#34;:\u0026#34;2020-12-15T12:25:05.3733901Z\u0026#34;} {\u0026#34;log\u0026#34;:\u0026#34;\\n\u0026#34;,\u0026#34;stream\u0026#34;:\u0026#34;stdout\u0026#34;,\u0026#34;time\u0026#34;:\u0026#34;2020-12-15T12:25:05.3734126Z\u0026#34;} {\u0026#34;log\u0026#34;:\u0026#34;For more examples and ideas, visit:\\n\u0026#34;,\u0026#34;stream\u0026#34;:\u0026#34;stdout\u0026#34;,\u0026#34;time\u0026#34;:\u0026#34;2020-12-15T12:25:05.3734351Z\u0026#34;} {\u0026#34;log\u0026#34;:\u0026#34; https://docs.docker.com/get-started/\\n\u0026#34;,\u0026#34;stream\u0026#34;:\u0026#34;stdout\u0026#34;,\u0026#34;time\u0026#34;:\u0026#34;2020-12-15T12:25:05.3734587Z\u0026#34;} {\u0026#34;log\u0026#34;:\u0026#34;\\n\u0026#34;,\u0026#34;stream\u0026#34;:\u0026#34;stdout\u0026#34;,\u0026#34;time\u0026#34;:\u0026#34;2020-12-15T12:25:05.3734814Z\u0026#34;} # コンテナから出る $ exit Docker Desktop for Macで動かしたDockerコンテナの標準出力の内容がログファイルとして閲覧できた.\nやったぜ.\nおわり Docker Desktop for MacでDockerを動かしているVMに入り, コンテナのログファイルを閲覧することができた.\nDockerがそもそもMacOSで直接動作していないことを理解できてなかったので, 勉強になった\u0026hellip;\nHyperKitじゃなくてVirtualBoxでmacOS上にVMを立ててDockerを動かす方法もあるみたいなので, 余裕があれば試してみたい.\nおまけ Install Docker Desktop on Mac\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nBretFisher/docker-for-mac.md\u0026#160;\u0026#x21a9;\u0026#xfe0e;\ndocker can\u0026rsquo;t access to volume with screen\u0026#160;\u0026#x21a9;\u0026#xfe0e;\njustincormack/nsenter1\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2020-12-15T21:10:35+09:00","image":"/post/2020-12-15-docker-desktop-for-mac-hyperkit-vm/sotochan.jpg","permalink":"/post/2020-12-15-docker-desktop-for-mac-hyperkit-vm/","title":"Docker Desktop for MacのHyperKit VMに入る"},{"content":"かんたんすぎる Kubernetesの動作確認環境として, 簡単にクラスタを用意できるkindを使ってみた.\nやったことのまとめ kindをMacにインストールした kindでKubernetesクラスタを作成, 削除した kindで作ったクラスタにIngress Controllerをデプロイした つかうもの macOS Mojave 10.14 Docker Desktop for Mac Version 2.5.0.0 Docker version 19.03.13 インストール済み anyenv 1.1.1 インストール済み goenv 2.0.0beta11 インストール済み go version go1.15.5 darwin/amd64 今回入れる kind v0.9.0 go1.15.5 darwin/amd64 今回入れる kubectl Client Version: v1.19.3 インストール済み やったこと kindってなに Goの更新 kindのインストール クラスタの起動 Ingressの設定 クラスタの削除 kindってなに kind1はDockerコンテナをNodeとして動かすことでローカル環境でKubernetesクラスタを動作させるツール.\n今までローカルでの動作確認にはDocker Desktopで作れるKuberentesクラスタを使ってたんだけど,\nこの前Kubernetes v1.20以降はDockerがNodeのコンテナランタイムとして非推奨2になるとのお知らせが出ていた.\nDocker DesktopのKubernetesクラスタはDockerをコンテナランタイムとして使っているので, そのうち使えなくなるかも\u0026hellip;\n# Docker Desktopで立てたKubernetesクラスタのコンテナランタイムはDocker $ kubectl get nodes -o wide --context=docker-desktop NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME docker-desktop Ready master 33d v1.19.3 192.168.65.3 \u0026lt;none\u0026gt; Docker Desktop 5.4.39-linuxkit docker://19.3.13 ということで, これを機にローカルでの動作確認用Kubernetesクラスタはkindで作ることにした.\n(kindではNodeでcontainerd3をランタイムとして使っているので問題ないらしい)\nGoの更新 (Goの最新版がインストールされている環境なら飛ばしてOK)\nkindのクイックスタート4によるとGoの最新版を使ってね!とのことなのでせっかくだし新しいGo(1.15)をインストールする.\n$ go version go version go1.14.6 darwin/amd64 # goenvを更新 $ anyenv install goenv anyenv: /Users/uzimihsr/.anyenv/envs/goenv already exists Reinstallation keeps versions directories continue with installation? (y/N) y ... Install goenv succeeded! Please reload your profile (exec $SHELL -l) or open a new session. # シェルを再起動 $ exec $SHELL -l # インストール可能なバージョンの一覧を確認 $ goenv install -l | grep 1.15 1.15.0 1.15beta1 1.15rc2 1.15.1 1.15.2 1.15.3 1.15.4 1.15.5 # Go 1.15.5をインストール $ goenv install 1.15.5 Downloading go1.15.5.darwin-amd64.tar.gz... -\u0026gt; https://golang.org/dl/go1.15.5.darwin-amd64.tar.gz Installing Go Darwin 64bit 1.15.5... Installed Go Darwin 64bit 1.15.5 to /Users/uzimihsr/.anyenv/envs/goenv/versions/1.15.5 # このMacで常にGo 1.15.5を使うようにする $ goenv global 1.15.5 $ goenv rehash # 確認 $ goenv versions 1.12.9 1.13.0 1.14.6 * 1.15.5 (set by /Users/uzimihsr/.anyenv/envs/goenv/version) $ go version go version go1.15.5 darwin/amd64 $ echo $GOPATH /Users/uzimihsr/go/1.15.5 OK.\nkindのインストール 次にkindをインストールする.\nといってもgo getで入るのでインストール自体は1行で終わっちゃう.\nクッソかんたん.\n# kindのインストール $ GO111MODULE=\u0026#34;on\u0026#34; go get sigs.k8s.io/kind@v0.9.0 # $GOPATH/bin/kindに入っている $ which kind /Users/uzimihsr/go/1.15.5/bin/kind # 動作確認 $ kind version kind v0.9.0 go1.15.5 darwin/amd64 クラスタの起動 いよいよKubernetesクラスタを起動する.\n\u0026hellip;これもコマンド1発でできる.\nめっちゃかんたん.\n# Kubernetesクラスタ(kind-kind)の作成 $ kind create cluster Creating cluster \u0026#34;kind\u0026#34; ... ✓ Ensuring node image (kindest/node:v1.19.1) 🖼 ✓ Preparing nodes 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 Set kubectl context to \u0026#34;kind-kind\u0026#34; You can now use your cluster with: kubectl cluster-info --context kind-kind Not sure what to do next? 😅 Check out https://kind.sigs.k8s.io/docs/user/quick-start/ kubectlのcontextが勝手に切り替わるので, すぐにクラスタの操作もできる.\n# kubectlのcontextが自動で変更されている $ kubectl config view --minify apiVersion: v1 clusters: - cluster: certificate-authority-data: DATA+OMITTED server: https://127.0.0.1:63917 name: kind-kind contexts: - context: cluster: kind-kind user: kind-kind name: kind-kind current-context: kind-kind kind: Config preferences: {} users: - name: kind-kind user: client-certificate-data: REDACTED client-key-data: REDACTED # うまく切り替わっていない場合もkindコマンドから有効なkubeconfigを取得可能 $ kind get kubeconfig --name kind apiVersion: v1 clusters: - cluster: certificate-authority-data: ... server: https://127.0.0.1:63917 name: kind-kind contexts: - context: cluster: kind-kind user: kind-kind name: kind-kind current-context: kind-kind kind: Config preferences: {} users: - name: kind-kind user: client-certificate-data: ... client-key-data: ... # 動作確認 $ kubectl cluster-info --context kind-kind Kubernetes master is running at https://127.0.0.1:63917 KubeDNS is running at https://127.0.0.1:63917/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use \u0026#39;kubectl cluster-info dump\u0026#39;. 実際にNode用のコンテナが起動していることも確認できる.\n# Nodeが1つ作成されている(コンテナランタイムがcontainerd!) $ kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME kind-control-plane Ready master 19m v1.19.1 172.19.0.2 \u0026lt;none\u0026gt; Ubuntu Groovy Gorilla (development branch) 5.4.39-linuxkit containerd://1.4.0 # Nodeとして動いているコンテナ $ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 24117f8d397f kindest/node:v1.19.1 \u0026#34;/usr/local/bin/entr…\u0026#34; 21 minutes ago Up 20 minutes 127.0.0.1:63917-\u0026gt;6443/tcp kind-control-plane ちゃんとPodも動く. すごい.\n# Podも作れる $ kubectl run nginx --image=nginx $ kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx 1/1 Running 0 37s 10.244.0.5 kind-control-plane \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; # Podの動作確認 $ kubectl run busybox --image=busybox --restart=Never --rm -it -- wget -O- 10.244.0.5:80 Connecting to 10.244.0.5:80 (10.244.0.5:80) writing to stdout \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;Welcome to nginx!\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1\u0026gt;Welcome to nginx!\u0026lt;/h1\u0026gt; \u0026lt;p\u0026gt;If you see this page, the nginx web server is successfully installed and working. Further configuration is required.\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;For online documentation and support please refer to \u0026lt;a href=\u0026#34;http://nginx.org/\u0026#34;\u0026gt;nginx.org\u0026lt;/a\u0026gt;.\u0026lt;br/\u0026gt; Commercial support is available at \u0026lt;a href=\u0026#34;http://nginx.com/\u0026#34;\u0026gt;nginx.com\u0026lt;/a\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;\u0026lt;em\u0026gt;Thank you for using nginx.\u0026lt;/em\u0026gt;\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; - 100% |********************************| 612 0:00:00 ETA written to stdout pod \u0026#34;busybox\u0026#34; deleted Ingressの設定 簡単なPodやJobの動作確認だけならこれでも良いんだけど,\nクラスタ外からの動作確認がしたい場合は追加でIngressの設定が必要なので試してみる.\nまずは新たなクラスタを設定ファイル5を使って作成する.\nextraPortMappingsとnode-labelsが設定されていることが重要らしい.\n# kindのクラスタ設定ファイルの作成 $ vim config.yaml # 新規クラスタ(kind-ingress-enabled)の起動 $ kind create cluster --name ingress-enabled --config config.yaml Creating cluster \u0026#34;ingress-enabled\u0026#34; ... ✓ Ensuring node image (kindest/node:v1.19.1) 🖼 ✓ Preparing nodes 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 Set kubectl context to \u0026#34;kind-ingress-enabled\u0026#34; You can now use your cluster with: kubectl cluster-info --context kind-ingress-enabled Thanks for using kind! 😊 作ったばかりのクラスタ(kind-ingress-enabled)にIngress Controllerをデプロイする6.\n今回はNGINX Ingress Controller7を使用する.\n# NGINX Ingress Controllerのデプロイ $ kubectl apply --context kind-ingress-enabled -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/provider/kind/deploy.yaml namespace/ingress-nginx created serviceaccount/ingress-nginx created configmap/ingress-nginx-controller created clusterrole.rbac.authorization.k8s.io/ingress-nginx created clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created role.rbac.authorization.k8s.io/ingress-nginx created rolebinding.rbac.authorization.k8s.io/ingress-nginx created service/ingress-nginx-controller-admission created service/ingress-nginx-controller created deployment.apps/ingress-nginx-controller created validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created serviceaccount/ingress-nginx-admission created clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created role.rbac.authorization.k8s.io/ingress-nginx-admission created rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created job.batch/ingress-nginx-admission-create created job.batch/ingress-nginx-admission-patch created # 起動確認 $ kubectl wait --context kind-ingress-enabled --namespace ingress-nginx \\ --for=condition=ready pod \\ --selector=app.kubernetes.io/component=controller \\ --timeout=90s pod/ingress-nginx-controller-6df69bd4f7-57bkk condition met これでこのクラスタでIngressを使う準備ができた.\n最後にサンプルアプリ8を動かして, Ingressの動作を確認する.\n# サンプルアプリ(Pod, Service, Ingress)のデプロイ $ kubectl apply -f https://kind.sigs.k8s.io/examples/ingress/usage.yaml pod/foo-app created service/foo-service created pod/bar-app created service/bar-service created Warning: networking.k8s.io/v1beta1 Ingress is deprecated in v1.19+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress ingress.networking.k8s.io/example-ingress created # リクエストに対してそれぞれfoo, barを返すPodとそれに対応したService $ kubectl get all NAME READY STATUS RESTARTS AGE pod/bar-app 1/1 Running 0 33s pod/foo-app 1/1 Running 0 33s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/bar-service ClusterIP 10.96.251.3 \u0026lt;none\u0026gt; 5678/TCP 33s service/foo-service ClusterIP 10.96.170.161 \u0026lt;none\u0026gt; 5678/TCP 33s service/kubernetes ClusterIP 10.96.0.1 \u0026lt;none\u0026gt; 443/TCP 16m # /foo, /barへのトラフィックをそれぞれのServiceに振り分けるIngress $ kubectl get ingress Warning: extensions/v1beta1 Ingress is deprecated in v1.14+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress NAME CLASS HOSTS ADDRESS PORTS AGE example-ingress \u0026lt;none\u0026gt; * localhost 80 16m $ kubectl describe ingress example-ingress Warning: extensions/v1beta1 Ingress is deprecated in v1.14+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress Name: example-ingress Namespace: default Address: localhost Default backend: default-http-backend:80 (\u0026lt;error: endpoints \u0026#34;default-http-backend\u0026#34; not found\u0026gt;) Rules: Host Path Backends ---- ---- -------- * /foo foo-service:5678 10.244.0.10:5678) /bar bar-service:5678 10.244.0.9:5678) Annotations: \u0026lt;none\u0026gt; Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Sync 17m (x2 over 17m) nginx-ingress-controller Scheduled for sync # クラスタ外から叩いて動作確認 $ curl localhost/foo foo $ curl localhost/bar bar やったぜ.\nkindで立てたクラスタでもIngressを使ってクラスタ外からアクセスすることができた.\nクラスタの削除 最後にクラスタをお掃除する.\nこれもコマンド1発なのでめちゃくちゃ簡単.\n# 削除前の状態 $ kind get clusters ingress-enabled kind # Node用のコンテナが2クラスタぶん存在する $ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES d5c5aa156df9 kindest/node:v1.19.1 \u0026#34;/usr/local/bin/entr…\u0026#34; 23 hours ago Up 23 hours 0.0.0.0:80-\u0026gt;80/tcp, 0.0.0.0:443-\u0026gt;443/tcp, 127.0.0.1:57487-\u0026gt;6443/tcp ingress-enabled-control-plane 24117f8d397f kindest/node:v1.19.1 \u0026#34;/usr/local/bin/entr…\u0026#34; 24 hours ago Up 24 hours 127.0.0.1:63917-\u0026gt;6443/tcp kind-control-plane # クラスタの削除 $ kind delete cluster --name kind Deleting cluster \u0026#34;kind\u0026#34; ... # 一括削除も可能 $ kind delete clusters --all Deleted clusters: [\u0026#34;ingress-enabled\u0026#34;] # Nodeのコンテナがすべて消えている $ docker container ls -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES # kubeconfigの設定も勝手に消えている(残っているのは別途作成したDocker DesktopとMinikubeの設定のみ) $ kubectl config view apiVersion: v1 clusters: - cluster: certificate-authority-data: DATA+OMITTED server: https://kubernetes.docker.internal:6443 name: docker-desktop - cluster: certificate-authority: /Users/uzimihsr/.minikube/ca.crt server: https://192.168.99.110:8443 name: minikube contexts: - context: cluster: docker-desktop user: docker-desktop name: docker-desktop - context: cluster: minikube user: minikube name: minikube current-context: \u0026#34;\u0026#34; kind: Config preferences: {} users: - name: docker-desktop user: client-certificate-data: REDACTED client-key-data: REDACTED - name: minikube user: client-certificate: /Users/uzimihsr/.minikube/client.crt client-key: /Users/uzimihsr/.minikube/client.key \u0026hellip;こんなに簡単でいいのか?\nおわり kindを使ったKubernetesクラスタの作成, Ingress Controllerのデプロイ, クラスタの削除を一通り試してみた.\nIngress対応だけちょっと面倒だけどそれもほぼコピペでできるし,\n基本はコマンド1行でクラスタが簡単に作れるのがすごい. 便利すぎる.\nおまけ https://kind.sigs.k8s.io/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nDon\u0026rsquo;t Panic: Kubernetes and Docker\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://containerd.io/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://kind.sigs.k8s.io/docs/user/quick-start\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://kind.sigs.k8s.io/docs/user/ingress/#create-cluster\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://kind.sigs.k8s.io/docs/user/ingress/#ingress-nginx\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/kubernetes/ingress-nginx/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://kind.sigs.k8s.io/docs/user/ingress/#using-ingress\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2020-12-14T22:13:17+09:00","image":"/post/2020-12-14-kind-kubernetes-in-docker/sotochan.jpg","permalink":"/post/2020-12-14-kind-kubernetes-in-docker/","title":"kind(Kubernetes IN Docker)を試した"},{"content":"鬼滅のテスト 無限ループ編 わかんなかったので自分なりにやってみたメモ\nサブタイトルはおふざけ\nまとめ ループ1回分の処理を抜き出して, それをテストするのが良さそう.\nループ1回分のモックが作れるなら, 無限ループの部分を呼び出して任意の回数でループを止めることもできる.\n(無限ループを含むコード例)\n(テストコード例)\n環境 macOS Mojave 10.14 go version go1.14.6 darwin/amd64 やりかた Goでこんな感じの無限ループ処理があるときに,\nどうやってテストを書いたらいいかわかんなかった.\n# 処理としてはhogehoge.txtの内容を1秒ごとに読み込んで出力するだけ $ echo fugafuga \u0026gt; hogehoge.txt $ go run main.go fugafuga fugafuga fugafuga fugafuga fugafuga ^Csignal: interrupt # 無限ループなので強制終了する テキトーに調べたけど1, 提案されているのは\nループの回数を指定できるような仕組みを仕込んでおいてテストのときだけ回数を有限にする ループ1回分の処理だけをテストするようにする のどっちかという感じだった.\n自分の場合はDI(Dependency Injection)以外でテストのためだけに変な引数を追加したりしたくないので, ループ1回分だけを取り出してテストするほうが良いかな, と思った.\nまずはmain.goを修正して, ループ1回分の処理を別のメソッド(Loop.Run)に分ける.\n(実行される処理の内容は変わらない)\n次にgomock2でループ1回分のinterface(Loop)のモックを作る.\n# go.modの作成($GOPATH配下で作業している場合は不要) $ go mod init github.com/uzimihsr/infinite-loop-test go: creating new go.mod: module github.com/uzimihsr/infinite-loop-test # mockの生成 $ go get github.com/golang/mock/mockgen@v1.4.4 $ mockgen -source=./main.go -destination=mock_main.go -package=main Loop mock_main.go(生成されたモック) (後で気づいたけど, main関数のテストはしないのでちゃんとパッケージを分ければよかった\u0026hellip;)\nループ1回分(Loop.Run)のテストと無限ループ(InfiniteLoop.Run)のテストを作成する.\nループ1回分のテストでは実際に1回分の処理だけをテストしている.\n(今回はloop.Run()内部で標準パッケージのioutil.ReadFile()を直接呼び出してしまっているけど, 可能であればここも依存している関数やメソッドのモックを使ったほうが良い気がする)\n無限ループのテストではmockgenで生成したループ1回分のモックを利用して,\n任意の回数を超えた時点でエラーを発生させてループが止まるようにしている.\nこうすることで, 結果的には無限ループの回数を指定してテストしているのと同じことができた.\n実際にテストを実行してみる.\n# 最終的な作業ディレクトリの状態 $ tree . . ├── go.mod ├── go.sum ├── hogehoge.txt ├── main.go ├── main_test.go ├── mock_main.go └── test-hogehoge.txt # テスト実行 $ echo test-fugafuga \u0026gt; test-hogehoge.txt $ go test -v -cover ./... === RUN TestLoop === RUN TestLoop/正常にファイルが開けるケースのテスト test-fugafuga === RUN TestLoop/ファイルが開けず異常終了するケースのテスト --- PASS: TestLoop (0.00s) --- PASS: TestLoop/正常にファイルが開けるケースのテスト (0.00s) --- PASS: TestLoop/ファイルが開けず異常終了するケースのテスト (0.00s) === RUN TestInfiniteLoop === RUN TestInfiniteLoop/ループが1回正常に呼び出されることのテスト === RUN TestInfiniteLoop/ループが任意の回数(例えば10回)正常に呼び出されることのテスト --- PASS: TestInfiniteLoop (11.03s) --- PASS: TestInfiniteLoop/ループが1回正常に呼び出されることのテスト (1.01s) --- PASS: TestInfiniteLoop/ループが任意の回数(例えば10回)正常に呼び出されることのテスト (10.02s) PASS coverage: 63.4% of statements ok github.com/uzimihsr/infinite-loop-test\t11.034s\tcoverage: 63.4% of statements mock_main.goがmainパッケージに入ってたりmain関数のテストができていなかったりで,\nカバレッジは100%になっていないけど一応やりたかった内容は実行できた.\nテストの目的はカバレッジを100%にすることではないので, これでいいはず\u0026hellip;\nおわり 無限ループがあるコードを書いた経験があまりなかったので, けっこう詰まってしまった.\n自分なりにやってみたけど, 他にもっといい方法があるんだろうか\u0026hellip;\nもっと厳密にやりたいならosパッケージ3を呼び出してプロセスを扱ったりしてもいいんだろうけど, そこまでする必要があるかは謎🤔\n(個人的には\u0026quot;無限に繰り返されること\u0026quot;の確認にそこまでの価値があると思えなかった)\nおまけ unit testing - How to test code that loops forever - Stack Overflow\u0026#160;\u0026#x21a9;\u0026#xfe0e;\ngithub.com/golang/mock\u0026#160;\u0026#x21a9;\u0026#xfe0e;\ngolang.org/pkg/os\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2020-12-05T13:40:34+09:00","image":"/post/2020-12-05-test-infinite-loop-golang/sotochan.jpg","permalink":"/post/2020-12-05-test-infinite-loop-golang/","title":"Goで無限ループのテストの書き方がわかんなかった"},{"content":"もふもふ ちょっと寒くなってきたけどそとちゃんは相変わらず元気だった.\nまとめ まるい ハミ毛処理 ソファ まるい 11月もそとちゃんはころころしていた.\nなんだこれ pic.twitter.com/swNMCbukZs\n\u0026mdash; ずみし (@uzimihsr) November 10, 2020 まるい pic.twitter.com/sVjWH7icHI\n\u0026mdash; ずみし (@uzimihsr) November 12, 2020 早朝から運動会して, お昼ごろには疲れてまるくなってお昼寝してるのが最近のパターン.\n14時くらいにいったん起きてごはんを食べるけど, その後はまたまるくなって夕方くらいまで寝てる.\n遊び疲れ果てた pic.twitter.com/bTvAfAoYor\n\u0026mdash; ずみし (@uzimihsr) November 12, 2020 ねてる pic.twitter.com/QQz5Qbn34Z\n\u0026mdash; ずみし (@uzimihsr) November 18, 2020 寒いから丸まってるのかと思ってヒーターも買ったけど,\nエアコンと違って温風が直接当たるのがそんなに好きじゃないらしい.\nちゃんと離れてるのえらいねえ pic.twitter.com/1M0OmOhNjJ\n\u0026mdash; ずみし (@uzimihsr) November 17, 2020 横から見るとおまんじゅうみたいでかわいい.\nこれなに？ pic.twitter.com/V6J0WGABj8\n\u0026mdash; ずみし (@uzimihsr) November 20, 2020 ハミ毛処理 ときどきフローリングですべってるから怪しいとは思ってたけど, 足のハミ毛が立派に伸びていた.\n毛深いもみじ pic.twitter.com/egVKmWiyex\n\u0026mdash; ずみし (@uzimihsr) November 26, 2020 特に後ろ足のハミ毛が肉球を隠すくらい伸びていて, ちょっと危ないのでハミ毛の処理をしてみた.\nハサミでやるのはちょっと怖かったのでバリカンを購入.\nホームバーバー　エキスパートスタイル　コンパクト\n初めて知ったんだけど猫専用バリカンというものはなくて, だいたい小型犬とかと兼用のものが使えるらしい.\nバリカンに挑戦 pic.twitter.com/oDPNO2Pg2c\n\u0026mdash; ずみし (@uzimihsr) November 29, 2020 途中の写真は撮れてないけど, そとちゃんは今回も協力的でおとなしく剃らせてくれた.\nうちのねこはほんとにいい子で助かる.\n(バリカンの音が思ったよりうるさくなかったのが良かったかもしれない\u0026hellip;)\nハミ毛処理後の足はこんな感じ.\nとりあえず肉球がちゃんと見えるくらいまでハミ毛を剃れた pic.twitter.com/42ufY9RTdh\n\u0026mdash; ずみし (@uzimihsr) November 29, 2020 きれいなピンクの肉球がよく見えるようになってかわいい.\nねてる pic.twitter.com/41aBsgwCWP\n\u0026mdash; ずみし (@uzimihsr) November 30, 2020 ソファ 😭😭😭\nもうだめだ😭😭😭\n先月に引き続きソファバリバリするたびに叱ってたんだけど, どうやら\nバリバリする → しもべが寄ってくる = かまってもらえる!\nと学習してしまったようで😭\n最近は朝俺が起きたときや夕方の仕事が終わりかけてるときだったり,\nかまってもらえるタイミングを見計らってわざとバリバリしてるようにも見える\u0026hellip;\n自分が怒られてるとは微塵にも思ってないらしく, 現行犯で叱っても「なんか用？」みたいな顔をする.\nどうすりゃいいんだ\u0026hellip;😭\nおわり 11月もそとちゃんは良い子だった.\nこれからさらに寒くなるけどヒーターはあまり気に入ってもらえなかったので, 他の寒さ対策も考えたい.\nソファはもうダメかも\u0026hellip;😭\nおまけ ","date":"2020-12-03T12:09:40+09:00","image":"/post/2020-12-03-sotochan/sotochan07.jpg","permalink":"/post/2020-12-03-sotochan/","title":"11月のそとちゃんまとめ(2020)"},{"content":"環境構築が面倒 DBを使ったアプリケーションを開発するときに毎回MySQLやテーブルを準備するのが嫌だったので,\nコンテナを使ってMySQLが動作する環境を簡単に作成できるようにした.\nやったことのまとめ MySQLのコンテナをDockerで起動した docker-composeを使ってコンテナの起動時に初期設定用SQLが自動で実行されるようにした docker-compose.yml\n# コンテナを直接起動する例 $ docker container run -d -p 3306:3306 --name mysql-container -e MYSQL_ROOT_PASSWORD=\u0026lt;rootユーザのパスワードに設定したい文字列\u0026gt; mysql:5.7.32 # docker-composeで起動する例 # 上記docker-compose.ymlの他にpassword-root(rootパスワードを記述したファイル), member.sql初期設定用SQLファイルを用意して実行する $ docker-compose up -d # ホストOSからDBを操作するにはコンテナに入るか, 別のコンテナを建てるか, ホストOSのクライアントを使う $ docker container exec -it mysql-container mysql -u root -p $ docker container run --rm -it mysql:5.7.32 mysql -h host.docker.internal -P 3306 -u root -p $ mysql -h 127.0.0.1 -P 3306 -u root -p つかうもの macOS Mojave 10.14 Docker Desktop for Mac Version 2.5.0.0 Kubernetes: v1.19.3 Docker version 19.03.13 docker-compose version 1.27.4 MySQL (Docker image) Server version: 5.7.32 やったこと Dockerで起動する docker-composeで起動する Dockerで起動する MySQLの公式Docker image1が用意されているので, これを使う.\n# localhostからアクセスできるよう3306ポートを割り当てて起動 # 環境変数(MYSQL_ROOT_PASSWORD)でrootユーザのパスワード(hogehoge)を設定する $ docker container run -d -p 3306:3306 --name mysql-container -e MYSQL_ROOT_PASSWORD=hogehoge mysql:5.7.32 # コンテナに入ってMySQLクライアントを起動 $ docker container exec -it mysql-container mysql -u root -p Enter password: # hogehoge Welcome to the MySQL monitor. Commands end with ; or \\g. Your MySQL connection id is 3 Server version: 5.7.32 MySQL Community Server (GPL) Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type \u0026#39;help;\u0026#39; or \u0026#39;\\h\u0026#39; for help. Type \u0026#39;\\c\u0026#39; to clear the current input statement. mysql\u0026gt; show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | +--------------------+ 4 rows in set (0.01 sec) mysql\u0026gt; exit Bye # ホストOSのMySQLクライアントからも接続できる $ mysql -h 127.0.0.1 -P 3306 -u root -p Enter password: # hogehoge ... mysql\u0026gt; exit Bye # 使い捨てのコンテナからホストOSのlocalhost:3306で起動しているMySQLのコンテナに接続 $ docker container run --rm -it mysql:5.7.32 mysql -h host.docker.internal -P 3306 -u root -p Enter password: # hogehoge ... mysql\u0026gt; exit Bye # クライアント用のコンテナを使用するパターン # 事前にdocker network(bridge)内のmysql-containerのIPを確認する $ docker container inspect mysql-container -f \u0026#34;{{.NetworkSettings.IPAddress}}\u0026#34; 172.17.0.2 $ docker container run --rm -it mysql:5.7.32 mysql -h 172.17.0.2 -P 3306 -u root -p Enter password: # hogehoge ... mysql\u0026gt; exit Bye # コンテナを削除 $ docker container rm -f mysql-container 流石にDockerなら簡単に動かせた.\ndocker-composeで起動する もうちょっと複雑なことがしたいので, docker-composeで起動してみる.\n# パスワードファイルの作成 $ vim password-root # パスワード文字列(hogehoge)を入力する $ cat password-root hogehoge # パスワードファイル(password-root), 初期設定用SQLファイル(member.sql), docker-compose.ymlが存在する状態 $ ls docker-compose.yml member.sql password-root # コンテナの起動 $ docker-compose up -d Starting mysql-container ... done # データベースの確認 # mysql -h 127.0.0.1 -P 3306 -u root -p でも同じことができる(ホストOSのMySQLクライアントを使う場合) $ docker-compose exec mysql mysql -u root -p Enter password: # hogehoge ... mysql\u0026gt; use mydb Database changed mysql\u0026gt; SELECT * FROM members; +----+----------+------+---------+------+ | id | name | team | role | age | +----+----------+------+---------+------+ | 1 | Alice | A | manager | 30 | | 2 | Ben | B | manager | 50 | | 3 | Charlie | A | member | 40 | | 4 | Daniel | A | member | 30 | | 5 | Emily | A | member | 20 | | 6 | Florence | A | member | 30 | | 7 | George | A | trainee | 20 | | 8 | Harry | B | member | 40 | | 9 | Isabel | B | member | 40 | | 10 | Jack | B | trainee | 20 | | 11 | Katie | B | trainee | 20 | +----+----------+------+---------+------+ 11 rows in set (0.00 sec) # 初期設定用SQLが実行されているので最初からデータベースとテーブルが作成されている ポイントは2つ.\n1つめはrootユーザのパスワードをファイルから読み込む形にしていること.\n先程使用した環境変数のMYSQL_ROOT_PASSWORDではなくMYSQL_ROOT_PASSWORD_FILEでファイルのパスを指定すればその中身をパスワードとして設定できるので,\nパスワード文字列(hogehoge)が記述されたファイル(password-root)をホストOSからコンテナにマウントしてそのパスをMYSQL_ROOT_PASSWORD_FILEで指定するようにしている.\nこうするとdocker-compose.ymlからパスワードの情報を分離できて,\nそのままリポジトリとかで共有できるようになる.\n2つめはデータベースの初期設定用のSQLがコンテナの起動時に勝手に実行されるようにしていること.\nコンテナのディレクトリ/docker-entrypoint-initdb.dの配下にある拡張子が.sh, .sql, .sql.gzのスクリプトは勝手に実行されるので,\nここに実行したいSQLファイル(member.sql)をマウントしている.\nこうするとわざわざ起動後のコンテナに入って初期設定用のSQLを実行する必要がなくなり,\ndocker-composeを立ち上げるだけで使いたいデータベースとテーブルが準備できるので便利.\nおわり Dockerとdocker-composeを使ってMySQLを起動する手順を試してみた.\n特にdocker-composeを使う方法だと設定ファイルさえ用意すればコマンド1つでレコード入りのテーブルが簡単に用意できるのでめっちゃ便利だと思う.\nおまけ https://hub.docker.com/_/mysql\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2020-11-27T12:10:37+09:00","image":"/post/2020-11-27-mysql-on-docker/sotochan.jpg","permalink":"/post/2020-11-27-mysql-on-docker/","title":"Dockerとdocker-composeでMySQLを動かした"},{"content":"それもそう これも大した話ではないんだけど勉強になったのでメモ.\nまとめ Kubernetes APIの/versionをたたけばversion情報を取得できる.\n# kube-apiserverのversion情報を取得 $ curl -s --insecure https://\u0026lt;Kubernetes API\u0026gt;/version | jq \u0026#39;.gitVersion\u0026#39; # jqが使えない場合でも生のjsonのgitVersionから読み取れる $ curl --insecure https://\u0026lt;Kubernetes API\u0026gt;/version 環境 macOS Mojave 10.14 Docker Desktop for Mac Version 2.5.0.0 Kubernetes: v1.19.3 jq-1.6 やりかた KubernetesクラスタのAPIのバージョンを調べたいとき, kubectlが使えるならkubectl versionで確認できる.\n# Docker DesktopのKubernetesクラスタを使用 $ kubectl cluster-info Kubernetes master is running at https://kubernetes.docker.internal:6443 KubeDNS is running at https://kubernetes.docker.internal:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use \u0026#39;kubectl cluster-info dump\u0026#39;. # Kubernetesのversionを確認 $ kubectl version --short Client Version: v1.19.3 # クライアント(kubectl)のversion Server Version: v1.19.3 # サーバー(kube-apiserver)のversion これをkubectlが使えない環境で確認する方法がわかんなかった.\nとはいえkubectlも中身はwebクライアントなので, API側にHTTPリクエストを飛ばして結果を表示しているはず。\nということでkubectlのコードを読んでみた.\nkubectl versionを実行したときの処理はたぶんここ1.\n呼び出されているdiscoveryClient.ServerVersion()の処理はおそらくここ2.\nAPIエンドポイントの/versionにGETを飛ばしてるだけっぽい.\nというわけでcurlでやってみる.\n今回使うKubernetesクラスタはDocker Desktopで立てたものなのでクライアント認証をしているが,\n他の認証方法を使っているクラスタの場合でもエンドポイント等は同じはず.\n$ curl --insecure --cert ./client.crt --key ./client.key https://kubernetes.docker.internal:6443/version { \u0026#34;major\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;minor\u0026#34;: \u0026#34;19\u0026#34;, \u0026#34;gitVersion\u0026#34;: \u0026#34;v1.19.3\u0026#34;, \u0026#34;gitCommit\u0026#34;: \u0026#34;1e11e4a2108024935ecfcb2912226cedeafd99df\u0026#34;, \u0026#34;gitTreeState\u0026#34;: \u0026#34;clean\u0026#34;, \u0026#34;buildDate\u0026#34;: \u0026#34;2020-10-14T12:41:49Z\u0026#34;, \u0026#34;goVersion\u0026#34;: \u0026#34;go1.15.2\u0026#34;, \u0026#34;compiler\u0026#34;: \u0026#34;gc\u0026#34;, \u0026#34;platform\u0026#34;: \u0026#34;linux/amd64\u0026#34; } 取れた.\nせっかくなのでjqで欲しい文字列を抜いてみる.\n$ curl -s --insecure --cert ./client.crt --key ./client.key https://kubernetes.docker.internal:6443/version | jq \u0026#39;.gitVersion\u0026#39; \u0026#34;v1.19.3\u0026#34; やったぜ.\nkubectlを使わずにcurlでkube-apiserverのバージョン情報を取得することができた.\nちなみにコードを探さなくても, /をたたけば使えるエンドポイントの一覧が取得できた.\n$ curl --insecure --cert ./client.crt --key ./client.key https://kubernetes.docker.internal:6443/ { \u0026#34;paths\u0026#34;: [ \u0026#34;/api\u0026#34;, \u0026#34;/api/v1\u0026#34;, \u0026#34;/apis\u0026#34;, \u0026#34;/apis/\u0026#34;, ..., \u0026#34;/version\u0026#34; ] } まあ今回は解決したのでヨシ!\nおわり kubectlを使わずにcurlで直接Kubernetes APIをたたいてバージョン情報を確認する方法がわかった.\nKubernetes APIを呼び出して何かする処理を自分で0から実装しようとすると結構たいへんなので, kubectlのコードを読んで参考にするのが良さそう.\nやっぱりちゃんとコードを読むのは大事だと思った.\nというかkubectlが便利すぎる. 偉大.\nおまけ kubectl/pkg/cmd/version/version.go\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nclient-go/discovery/discovery_client.go\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2020-11-12T20:20:40+09:00","image":"/post/2020-11-12-check-kube-apiserver-version-curl/sotochan.jpg","permalink":"/post/2020-11-12-check-kube-apiserver-version-curl/","title":"kube-apiserverのversionをcurlで確認する"},{"content":"それはそう 冷静に考えれば大したことないんだけどちょっと詰まったので一応メモ.\nまとめ kubeconfigにベタ書きされているclient-certificate-data(クライアント証明書)とclient-key-data(秘密鍵)はbase64デコードするとファイルとして普通に使える.\n# kubeconfigにベタ書きされたクライアント証明書(client.crt)と秘密鍵(client.key)をファイルに出力する $ kubectl config view --minify --raw -o jsonpath=\u0026#39;{.users[].user.client-certificate-data}\u0026#39; | base64 --decode \u0026gt; client.crt $ kubectl config view --minify --raw -o jsonpath=\u0026#39;{.users[].user.client-key-data}\u0026#39; | base64 --decode \u0026gt; client.key # kubectlじゃなくてもOK ## awkでやるパターン $ cat ~/.kube/config | grep client-certificate-data | awk \u0026#39;{print $2}\u0026#39; | base64 --decode \u0026gt; client.crt $ cat ~/.kube/config | grep client-key-data | awk \u0026#39;{print $2}\u0026#39; | base64 --decode \u0026gt; client.key ## yqつかうパターン $ cat ~/.kube/config | yq r - \u0026#34;users[*].user.client-certificate-data\u0026#34; | base64 --decode \u0026gt; client.crt $ cat ~/.kube/config | yq r - \u0026#34;users[*].user.client-key-data\u0026#34; | base64 --decode \u0026gt; client.key # クライアント証明書(client.crt)と秘密鍵(client.key)を使ってcurlでKubernetes APIにアクセス $ curl --insecure --cert ./client.crt --key ./client.key https://\u0026lt;Kubernetes API\u0026gt; 環境 macOS Mojave 10.14 Docker Desktop for Mac Version 2.5.0.0 Kubernetes: v1.19.3 yq version 3.3.2 やりかた Docker Desktopで作ったKubernetesクラスタへのクライアント認証の情報は~/.kube/config(kubeconfig)に自動でベタ書きされている.\n# client-certificate-data : クライアント証明書 # client-key-data : クライアントの秘密鍵 $ kubectl config view --minify --raw apiVersion: v1 clusters: - cluster: certificate-authority-data: LS0t...Cg== server: https://kubernetes.docker.internal:6443 name: docker-desktop contexts: - context: cluster: docker-desktop user: docker-desktop name: docker-desktop current-context: docker-desktop kind: Config preferences: {} users: - name: docker-desktop user: client-certificate-data: LS0t...LS0K client-key-data: LS0t...Cg== このクラスタのAPIへcurlで接続しようとすると, クライアント認証で弾かれてしまう.\n# k8s API側がオレオレ証明書なので --insecure が必要 $ curl --insecure https://kubernetes.docker.internal:6443 { \u0026#34;kind\u0026#34;: \u0026#34;Status\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;metadata\u0026#34;: { }, \u0026#34;status\u0026#34;: \u0026#34;Failure\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;forbidden: User \\\u0026#34;system:anonymous\\\u0026#34; cannot get path \\\u0026#34;/\\\u0026#34;\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;Forbidden\u0026#34;, \u0026#34;details\u0026#34;: { }, \u0026#34;code\u0026#34;: 403 } おとなしくkubectlを使えばいいんだけど, 理由はさておきどうしてもcurlで接続したいことがあった.\ncurlでクライアント認証を突破するには--cert(クライアント証明書)と--key(クライアント秘密鍵)が必要なので,\nkubeconfigにベタ書きされている値をbase64でデコードする.\n# クライアント証明書をbase64デコード $ kubectl config view --minify --raw -o jsonpath=\u0026#39;{.users[].user.client-certificate-data}\u0026#39; | base64 -D -----BEGIN CERTIFICATE----- MIIDFTCCAf2gAwIBAgIICzCv4rM20vUwDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE ... QCUVil5khgn66X0Pd2GAs37k66Yyx7urGw== -----END CERTIFICATE----- # クライアントの秘密鍵をbase64デコード $ kubectl config view --minify --raw -o jsonpath=\u0026#39;{.users[].user.client-key-data}\u0026#39; | base64 -D -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAx6UtMTtlcvZWKPiQcDSlP7Ic2b2QOpigVifG6HOU5OtBc+Fn ... PEUMoWLF7jKfAGRtKrnY9DTE4HeDoohXlDG47KC+TLrm1bSwxMIn -----END RSA PRIVATE KEY----- よくみるPEM形式の証明書と秘密鍵が確認できる.\nあとはこれをファイルに出力するだけ.\n# クライアント証明書(client.crt)と秘密鍵(client.key)をファイルに出力する $ kubectl config view --minify --raw -o jsonpath=\u0026#39;{.users[].user.client-certificate-data}\u0026#39; | base64 -D -o client.crt $ kubectl config view --minify --raw -o jsonpath=\u0026#39;{.users[].user.client-key-data}\u0026#39; | base64 -D -o client.key 作成したクライアント証明書と秘密鍵のファイルを使って再度KubernetesクラスタのAPIを叩いてみる.\n# クライアント証明書(client.crt)と秘密鍵(client.key)を使って認証を突破 $ curl --insecure --cert ./client.crt --key ./client.key https://kubernetes.docker.internal:6443 { \u0026#34;paths\u0026#34;: [ \u0026#34;/api\u0026#34;, \u0026#34;/api/v1\u0026#34;, ..., \u0026#34;/version\u0026#34; ] } クライアント認証を突破できた.\nやったぜ.\n今回はクライアント証明書と秘密鍵をkubeconfigから取り出すのにkubectlを使ったけど,\nyaml形式のファイルから任意の値を取り出せるならもちろん他の方法でもできる.\nkubectlが使えなくてkubeconfigしかないような場合はこっちの方法を使うことが多そう.\n# grepとawkでやる例 $ cat ~/.kube/config | grep client-certificate-data | awk \u0026#39;{print $2}\u0026#39; | base64 -D $ cat ~/.kube/config | grep client-key-data | awk \u0026#39;{print $2}\u0026#39; | base64 -D # yqでやる例 $ cat ~/.kube/config | yq r - \u0026#34;users[*].user.client-certificate-data\u0026#34; | base64 -D $ cat ~/.kube/config | yq r - \u0026#34;users[*].user.client-key-data\u0026#34; | base64 -D # いずれも出力はkubectl config viewから取り出したときと同じ おわり kubectlは使えないけどkubeconfigは存在する, みたいな環境でどうしてもKubernetesクラスタのAPIをたたく必要があったので一応やってみた.\nkubectlが使える環境だけどcurlでのアクセスを試したい, というような場合は事前にServiceAccountを作ってBearer Tokenを発行したほうがたぶんはやい1.\nおまけ Kubernetes Docs\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2020-11-10T21:43:47+09:00","image":"/post/2020-11-10-generate-client-certificate-and-key-from-kubeconfig/sotochan.jpg","permalink":"/post/2020-11-10-generate-client-certificate-and-key-from-kubeconfig/","title":"kubeconfigにベタ書きされたclient-certificate-dataをファイル化して使う"},{"content":"ドラキュラねこ 10月のそとちゃんは新居にも慣れて元気いっぱいだった.\nまとめ ソファだいすき ハロウィン ソファだいすき 引っ越しに合わせて買ったソファがだいぶ気に入ったらしく, 最近はずっとごろごろしている.\nしろくてかわいいね pic.twitter.com/Q4Kz1wf518\n\u0026mdash; ずみし (@uzimihsr) October 1, 2020 ソファを占拠 pic.twitter.com/zSil5iCdUf\n\u0026mdash; ずみし (@uzimihsr) October 7, 2020 気に入ってくれたのは良かったんだけど,\nちょっと座ろうと思ってもど真ん中で寝てたりするのでたまに座れなくて困る.\nどうして人間2人がけのソファをねこ1匹で占領するのか\u0026hellip;🤔\nまた, 最近寒くなってきたおかげで俺がソファに座ってるとひざの上に乗ってくれる. うれしい.\n家の暖房を点けるかどうかで家族会議中\n(議長:ねこ) pic.twitter.com/dOv8elCMme\n\u0026mdash; ずみし (@uzimihsr) October 15, 2020 仕事させる気0 pic.twitter.com/2rthPAJQLj\n\u0026mdash; ずみし (@uzimihsr) October 22, 2020 そとちゃんに乗られると柔らかいし暖かいしとても幸せ(何もできなくなるけど).\n流石に仕事中は困るので降りていただくことになるが, その後のプレッシャーがすごい\u0026hellip;😭\nひざから降ろされたときの顔 pic.twitter.com/KETV5dBnve\n\u0026mdash; ずみし (@uzimihsr) October 22, 2020 そして危惧していた事態が現実に\u0026hellip;\nなんとそとちゃんがソファで爪とぎするようになってしまった.\n買ってから最初の数週間は爪を立てるそぶりも無かったのに,\nどのタイミングで気づいたのかはわからないけど爪が布にひっかかっておもしろくなっちゃったらしい\u0026hellip;\n我が家の教育方針はのびのび教育, 自由に育てるがモットーなのであまり叱らないようにしているんだけど,\nボロボロにされて中身が出ちゃったりすると誤飲とかが怖いので今回ばかりはちょっと怒った.\nそしてこの顔である.\nソファバリバリにしたからめっちゃおこられた pic.twitter.com/HYGaXuNwVC\n\u0026mdash; ずみし (@uzimihsr) October 28, 2020 ねこには日本語が通じないので, 全く効果なし\u0026hellip;\nソファをバリバリしてる場面を見つけ次第やめさせてるんだけど,\nそとちゃんは賢いので俺が寝てる間に思う存分バリバリしているっぽい.\nソファのすぐ横にお気に入りのつめとぎもあるんですけどね\u0026hellip;😭\nハロウィン 今年のハロウィンもそとちゃんにドラキュラになってもらった.\nドラキュラそとちゃん pic.twitter.com/WnBGWyKpSZ\n\u0026mdash; ずみし (@uzimihsr) October 31, 2020 着せてるのは去年と同じ100均のペット用マントだけど,\nこれくらい作りがシンプル(首とお腹に巻くだけ)だとつけててもそこまで嫌じゃないらしく着せてる間も協力的だった.\n宇宙一かわいいねえ\nおやついくらでもあげちゃう pic.twitter.com/5Osrj2dvYT\n\u0026mdash; ずみし (@uzimihsr) October 31, 2020 マントが似合っててめっちゃかわいい.\nなんかキメ顔してるように見えるのも最高.\nソファバリバリやねずみドリブルでお忙しい中俺のわがままに付き合ってもらったので,\nお礼に一番お気に入りのおやつをプレゼントした.\n1人じゃハロウィンなんて絶対スルーしてるけど, そとちゃんと一緒だと祝おうという気持ちになるのがふしぎ.\nいつまで祝えるかはわからないけど, できるだけ長く毎年祝えるようにしたい.\nおわり 10月のそとちゃんはちょっとわるいこと(ソファバリバリ)に目覚めてしまったけど, 基本は良い子にしてたし元気いっぱいだった.\nソファバリバリの件はこのまま放っておくとソファが破壊されてしまうので, どうにかしつけの方法を考えたい\u0026hellip;\nおまけ ","date":"2020-11-03T13:55:49+09:00","image":"/post/2020-11-03-sotochan/sotochan03.jpg","permalink":"/post/2020-11-03-sotochan/","title":"10月のそとちゃんまとめ(2020)"},{"content":"CI/CDっぽいことがしたい GitHub公式のCI/CDサービスGitHub Actionsを使って, リポジトリ上のDockerfileからimageをbuildしてpushする手順を試した.\nまとめ GitHubのPAT(個人アクセストークン)をリポジトリのSecretsにCR_PATとして登録した状態で以下のような.github/workflows/docker-publish.ymlを作成すると,\nmasterブランチへのcommitやrelease(tag)の作成時にDocker imageをGitHub ActionでGitHub Container Registryにpushすることができる.\npushしたimage : https://github.com/users/uzimihsr/packages/container/package/echo\n# ghcr.io/\u0026lt;GitHubアカウント\u0026gt;/\u0026lt;image名\u0026gt;:\u0026lt;タグ\u0026gt;のimageをGitHub Packagesにpushするworkflow name: Docker on: # masterブランチまたはvから始まるtag(例:`v1.2.3`)のpushでjobsを実行する push: branches: - master tags: - v* # 全ブランチのPRに対してもjobsを実行 pull_request: env: # \u0026lt;image名\u0026gt;を指定 IMAGE_NAME: echo jobs: # テスト用のjob test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Run tests run: | if [ -f docker-compose.test.yml ]; then docker-compose --file docker-compose.test.yml build docker-compose --file docker-compose.test.yml run sut else docker build . --file Dockerfile fi # imageをpushする push: # test jobが成功した場合のみトリガーされる needs: test runs-on: ubuntu-latest # pushイベント以外(PRなど)では実行されない if: github.event_name == \u0026#39;push\u0026#39; steps: - uses: actions/checkout@v2 - name: Build image run: docker build . --file Dockerfile --tag $IMAGE_NAME - name: Log into GitHub Container Registry # `read:packages`と`write:packages`の権限を持つPAT(個人アクセストークン)をSecretに`CR_PAT`として登録しておく # PATの作成手順 : https://docs.github.com/ja/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token run: echo \u0026#34;${{ secrets.CR_PAT }}\u0026#34; | docker login https://ghcr.io -u ${{ github.actor }} --password-stdin - name: Push image to GitHub Container Registry run: | # \u0026lt;GitHubアカウント\u0026gt;が自動で選択される IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME IMAGE_ID=$(echo $IMAGE_ID | tr \u0026#39;[A-Z]\u0026#39; \u0026#39;[a-z]\u0026#39;) VERSION=$(echo \u0026#34;${{ github.ref }}\u0026#34; | sed -e \u0026#39;s,.*/\\(.*\\),\\1,\u0026#39;) # git tagの値を\u0026lt;タグ\u0026gt;として付与 [[ \u0026#34;${{ github.ref }}\u0026#34; == \u0026#34;refs/tags/\u0026#34;* ]] \u0026amp;\u0026amp; VERSION=$(echo $VERSION | sed -e \u0026#39;s/^v//\u0026#39;) # masterブランチへのcommitでトリガーされている場合はlatestを\u0026lt;タグ\u0026gt;として使う [ \u0026#34;$VERSION\u0026#34; == \u0026#34;master\u0026#34; ] \u0026amp;\u0026amp; VERSION=latest echo IMAGE_ID=$IMAGE_ID echo VERSION=$VERSION docker tag $IMAGE_NAME $IMAGE_ID:$VERSION docker push $IMAGE_ID:$VERSION https://github.com/uzimihsr/echo-image/blob/master/.github/workflows/docker-publish.yml\n環境 macOS Mojave 10.14 GitHub GitHub Packages Git git version 2.20.1 (Apple Git-117) Docker Desktop for Mac Version 2.1.0.3 Docker Engine 19.03.13 やりかた PATの発行とログイン 手動でpush GitHub Actionsでpush PATの発行とログイン まずは公式ドキュメント1を参考に, GitHub Container Registryの認証に必要なPAT(個人アクセストークン)を発行する.\nGitHubにログインした状態で\nhttps://github.com/settings/tokens\nを開き, Generate new tokenをクリック.\nPATの権限設定画面ではwrite:packages, read:packagesにのみチェックを入れてGenetate tokenをクリック.\nPATが発行される.\n発行されたPATを使って, コマンドラインからGitHub Container Registryにログインする.\n# GitHub Container Registryにログイン $ echo \u0026lt;PAT\u0026gt; | docker login ghcr.io -u \u0026lt;GitHubアカウント\u0026gt; --password-stdin Login Succeeded これでログインは成功.\n手動でpush 上記の手順でGitHub Container Registryにログインした状態で, まずはコマンドラインからimageをpushしてみる.\n適当なimageを作成.\n# 適当なディレクトリで適当なDockerfileを作成 $ cd /path/to/workspace $ touch Dockerfile # Docker imageをビルド, 動作確認 $ docker image build -t echo:latest . $ docker container run --rm echo:latest hello, world! 次にこのimageにGitHub Container Registry用のタグをつけ, pushする.\n# imageのタグをつけ直す $ docker image tag echo:latest ghcr.io/uzimihsr/echo:latest # Docker imageをpush $ docker image push ghcr.io/uzimihsr/echo:latest The push refers to repository [ghcr.io/uzimihsr/echo] be8b8b42328a: Pushed latest: digest: sha256:c7926eae1c1aef6291e5eef1673c9815b5644fa9c417bfab65a4baba50c046a2 size: 527 pushしたimageの情報はGitHub PackagesのUIから確認できる.\nhttps://github.com/users/uzimihsr/packages/container/package/echo\nもちろんこのimageをpullして使うこともできる.\n# ローカルのimageを削除 $ docker image rm ghcr.io/uzimihsr/echo:latest # imageをpull $ docker image pull ghcr.io/uzimihsr/echo:latest latest: Pulling from uzimihsr/echo Digest: sha256:c7926eae1c1aef6291e5eef1673c9815b5644fa9c417bfab65a4baba50c046a2 Status: Downloaded newer image for ghcr.io/uzimihsr/echo:latest ghcr.io/uzimihsr/echo:latest # 動作確認 $ docker container run --rm ghcr.io/uzimihsr/echo:latest hello, world! これでGitHub Container Registryを使ったimageのpush/pullができるようになった.\nGitHub Actionsでpush 次にGitHub Actionsを使ってimageをGitHub Container Registryにpushしてみる.\n新たにGitHubリポジトリを作成し, 先程作成したDockerfileをgit pushする.\n作ったリポジトリ : https://github.com/uzimihsr/echo-image\n# Dockerfileが存在する状態 $ ls Dockerfile # git initからpushまで $ git init $ git remote add origin https://github.com/uzimihsr/echo-image.git $ git add . $ git commit -m \u0026#34;initial commit\u0026#34; $ git push origin master リポジトリのActionsタブを開き, Publish Docker ContainerアクションのSet up this workflowをクリック.\nworkflowの定義ファイルdocker-publish.ymlを編集する画面が開く.\nこのままでも動くけど, 自分用にenv.IMAGE_NAMEだけ任意のimage名に修正する.\ndocker-publish.yml # ghcr.io/\u0026lt;GitHubアカウント\u0026gt;/\u0026lt;image名\u0026gt;:\u0026lt;タグ\u0026gt;のimageをGitHub Packagesにpushするworkflow name: Docker on: # masterブランチまたはvから始まるtag(例:`v1.2.3`)のpushでjobsを実行する push: branches: - master tags: - v* # 全ブランチのPRに対してもjobsを実行 pull_request: env: # \u0026lt;image名\u0026gt;を指定 IMAGE_NAME: echo jobs: # テスト用のjob test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Run tests run: | if [ -f docker-compose.test.yml ]; then docker-compose --file docker-compose.test.yml build docker-compose --file docker-compose.test.yml run sut else docker build . --file Dockerfile fi # imageをpushする push: # test jobが成功した場合のみトリガーされる needs: test runs-on: ubuntu-latest # pushイベント以外(PRなど)では実行されない if: github.event_name == \u0026#39;push\u0026#39; steps: - uses: actions/checkout@v2 - name: Build image run: docker build . --file Dockerfile --tag $IMAGE_NAME - name: Log into GitHub Container Registry # `read:packages`と`write:packages`の権限を持つPAT(個人アクセストークン)をSecretに`CR_PAT`として登録しておく # PATの作成手順 : https://docs.github.com/ja/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token run: echo \u0026#34;${{ secrets.CR_PAT }}\u0026#34; | docker login https://ghcr.io -u ${{ github.actor }} --password-stdin - name: Push image to GitHub Container Registry run: | # \u0026lt;GitHubアカウント\u0026gt;が自動で選択される IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME IMAGE_ID=$(echo $IMAGE_ID | tr \u0026#39;[A-Z]\u0026#39; \u0026#39;[a-z]\u0026#39;) VERSION=$(echo \u0026#34;${{ github.ref }}\u0026#34; | sed -e \u0026#39;s,.*/\\(.*\\),\\1,\u0026#39;) # git tagの値を\u0026lt;タグ\u0026gt;として付与 [[ \u0026#34;${{ github.ref }}\u0026#34; == \u0026#34;refs/tags/\u0026#34;* ]] \u0026amp;\u0026amp; VERSION=$(echo $VERSION | sed -e \u0026#39;s/^v//\u0026#39;) # masterブランチへのcommitでトリガーされている場合はlatestを\u0026lt;タグ\u0026gt;として使う [ \u0026#34;$VERSION\u0026#34; == \u0026#34;master\u0026#34; ] \u0026amp;\u0026amp; VERSION=latest echo IMAGE_ID=$IMAGE_ID echo VERSION=$VERSION docker tag $IMAGE_NAME $IMAGE_ID:$VERSION docker push $IMAGE_ID:$VERSION https://github.com/uzimihsr/echo-image/blob/master/.github/workflows/docker-publish.yml\ndocker-publish.ymlの編集が終わったら, Start commit -\u0026gt; Commit new fileと進みworkflowを作成する.\n次にこのworkflowでGitHub Container Registryの認証を行うためのSecretsの設定をする.\nリポジトリのSettingsタブを開き, New sectetをクリック.\nSecretの作成画面ではNameをCR_PAT(docker-publish.yml内で参照している名前)に設定し, Valueには先程作成したPATを貼り付けてAdd secretをクリック.\nSecretが作成された.\nSecretを作成した状態でリポジトリのActionsタブを開くと,\n先程のdocker-publish.yml作成時のcommitで起動したworkflowが失敗している.\n(CR_PAT のSecretを作る前に実行されたため, 認証部分でコケている)\nRe-run all jobsから再度workflowを実行してみる.\n今度はCR_PATが作成済みなのでworkflowが正常に終了し, imageがghcr.io/uzimihsr/echo:latestにpushされた.\n再度GitHub Packageの画面\nhttps://github.com/users/uzimihsr/packages/container/package/echo\nを開くと, 確かにLast publishedが更新されている.\n以上でGitHub Actionsを使ってimageをGitHub Container Registryにpushする設定ができたので, 試しにimageを更新してみる.\n# masterからhogeブランチを切る $ git checkout master $ git pull origin master $ git checkout -b hoge # Dockerfileを修正 $ vim Dockerfile $ cat Dockerfile FROM busybox ENTRYPOINT [ \u0026#34;echo\u0026#34; ] CMD [ \u0026#34;good morning!\u0026#34; ] # CMDを変更した # commitしてhogeブランチをpush $ git add ./Dockerfile $ git commit -m \u0026#34;fixed Dockerfile: CMD\u0026#34; $ git push origin hoge Dockerfileの修正commitを積んだhogeブランチがpushされた状態でPR(master \u0026lt;- hoge)を作成すると, PRにトリガーされたworkflowが実行される.\ntestのjobのみが実行されpushのjobがスキップされているが, これは今回作成したdocker-publish.ymlでGitHub eventがpushの場合にのみ実行するよう設定しているため.\nif: github.event_name == \u0026#39;push\u0026#39; このPRをmergeすると今度はmasterブランチへのpushが行われるので, それにトリガーされたworkflowでtestとpushのjobが実行される.\n以降のimageがpushされるまでの流れは先程試した流れと同じで, latestタグのimageがpushされる.\nlatestタグだけではimageのバージョン管理が不便なので,\n最新版のimageにバージョンタグを付与してみる.\nやり方としては最新のcommitにgit tagをつけてpushするだけ.\n# masterブランチで作業 $ git checkout master $ git pull origin master # masterブランチの最新のcommitにvから始まるtagをつけてpushする $ git tag -a v0.0.1 -m \u0026#34;hogehoge\u0026#34; $ git push origin v0.0.1 するとtagのpushにトリガーされてworkflowが実行される.\n今度はtagで指定したvからはじまる文字列がimageのタグとして付与されていることがわかる.\n再度Packagesの画面を開くと確かにgit tagで指定した0.0.1のimageがpushされている.\n今回はコマンドラインでtagをつけたけど, UIからreleaseを作っても同様にimageのpushができる.\n最後にimageの動作確認を行う.\n# 動作確認 $ docker container run --rm ghcr.io/uzimihsr/echo:0.0.1 good morning! # CMDの変更が反映されている やったぜ.\nGitHub Actionsを使ってGitHub Container Registryにimageをタグ付けしてpushできるようになった.\nおわり GitHubリポジトリ上のDockerfileからimageを作れるようになった.\nGitHub Actionsを使うのも初めてだったので, 練習にもなってよかった.\nDocker Hubが無料プランだといろいろ制限が厳しくなってきてるので2, (今のところは無料の)GitHub Container Registryに乗り換えていこうと思う.\nおまけ https://docs.github.com/ja/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.docker.com/pricing/resource-consumption-updates\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2020-10-11T23:01:22+09:00","image":"/post/2020-10-11-github-action-publish-docker-image-ghcr/sotochan.jpg","permalink":"/post/2020-10-11-github-action-publish-docker-image-ghcr/","title":"Github Actionsを使ってDocker ImageをGitHub Container RegistryにPushする"},{"content":"新居 9月のそとちゃんは環境が変わって大変だった.\nまとめ おひっこし ねずみのおもちゃ おひっこし いろいろ事情があって, 9月に引っ越しをした.\nそとちゃんはうちに来る前は保護主さんのところで暮らしていたので, たぶん人間と一緒に引っ越すのは初めて.\n俺はもちろんねこと引っ越すのが初めてなので, いろいろ大変だった.\nまずは普段家に無いサイズのダンボールにそとちゃん大興奮.\n引っ越し用のダンボールを組み立てるたびに中に入って出てこないので, 荷物を詰めるのにめっちゃ時間がかかった\u0026hellip;\n勝手にダンボール入って出られなくなってる pic.twitter.com/TEUyKIQqOW\n\u0026mdash; ずみし (@uzimihsr) September 1, 2020 荷物詰めが終わったら終わったで, 積み上げたダンボールで大はしゃぎ.\nやっぱり猫はダンボール好きなんすね\u0026hellip;\nダンボールの王 pic.twitter.com/3hMV5pcpIs\n\u0026mdash; ずみし (@uzimihsr) September 6, 2020 今回頼んだ引っ越し業者は猫は一緒に運べなかったので, そとちゃんは荷物と別に車で移動.\n忙しすぎて写真を撮り忘れちゃったけど, 移動中はキャリーケースの中で不安そうに鳴いててちょっとかわいそうだった.😭\nでも新居は1日くらい探検したらすぐに慣れてくれて, すぐにそとちゃんの家になったみたい.\n特に新しく買ったソファと仕事用の机がお気に入り.\nゲーミングキャット pic.twitter.com/HMpT2Kwzw0\n\u0026mdash; ずみし (@uzimihsr) September 26, 2020 仕事監視ねこ pic.twitter.com/Ue1QjyWi8v\n\u0026mdash; ずみし (@uzimihsr) September 23, 2020 ねずみのおもちゃ また, だいぶ前に失くしたと思っていたねずみのおもちゃが引っ越しのおかげで見つかった.\nお気に入りのねずみのおもちゃがなくていじけてる\n(片付けたんじゃなくてそとちゃんが失くした) pic.twitter.com/tt1P6N25zy\n\u0026mdash; ずみし (@uzimihsr) June 1, 2020 なんと冷蔵庫の裏側, 壁との間に入っていた\u0026hellip;\nねこが入れる隙間なんてなかったのに, どうやったのか謎すぎる\u0026hellip;🤔\nこのねずみのおもちゃはまたたびのおまけに付いてきたやつで,\nそとちゃんの一番のお気に入り.\n失くしたときにあまりにも落ち込んでてかわいそうだったから同じメーカーの似たおもちゃを買ってあげたんだけど, おまけのねずみほどには食いつかなかった.\nペットショップ行けたのでバチクソおもちゃ買ってあげた(失くしたねずみのおもちゃ含む) pic.twitter.com/k4CZJNWD8r\n\u0026mdash; ずみし (@uzimihsr) June 2, 2020 このおもちゃ自体にまたたびの匂いがついてるとかそんなことはなくて, 売り物のおもちゃよりも色合いも地味.\nだけど一緒に出しても絶対おまけのおもちゃの方で遊ぶ. なぜ\u0026hellip;?\nきっとねこには人間ごときにはわからないこだわりがあるんだろう\u0026hellip;\nちなみに遊び方はちょっと変で,\nねずみを自分で咥えて放り投げる -\u0026gt; 落下点にダッシュ -\u0026gt; 落ちたねずみにねこパンチ -\u0026gt; 再度咥えて放り投げる\u0026hellip;\nをねずみが手の届かないところに入るまで延々と繰り返している.\n見ていてかなり面白いので, 今度動画を撮ってみたい.\nねずみ発見 pic.twitter.com/DYX4u7wTMP\n\u0026mdash; ずみし (@uzimihsr) September 30, 2020 ひとりで楽しそうに遊んでくれるのでしもべとしては楽なんだけど,\nすぐに失くすし, 誤飲の前科もあるので頃合いを見計らって片付けるようにしている.\nねずみのおもちゃ出しっぱなしにしたら案の定すぐ失くしてふてくされてる pic.twitter.com/BJnfvgyx8y\n\u0026mdash; ずみし (@uzimihsr) September 30, 2020 片付けるとぷーぷー文句言うのがちょっとかわいい.😂\nねずみのおもちゃを片付けられてきげんわるい pic.twitter.com/CrZbjrzkXS\n\u0026mdash; ずみし (@uzimihsr) September 28, 2020 おわり 9月のそとちゃんは環境の変化にもすぐに対応して, 元気いっぱいだった.\nほんとに手がかからない良い子でいつも助かる\u0026hellip;\nねずみのおもちゃも見つかって, 運動不足も解消できそうでほんとに良かった.\nおまけ ","date":"2020-10-07T22:26:28+09:00","image":"/post/2020-10-07-sotochan/sotochan04.jpg","permalink":"/post/2020-10-07-sotochan/","title":"9月のそとちゃんまとめ(2020)"},{"content":"Podを見張りたい Go用のKubernetesクライアントを使ってクラスタ内のPodの様子を見張るやつを作った.\nやったことのまとめ Kubernetesクラスタ内のPodをwatchし, Podの状態変化に応じて任意の処理を行うobserver.goを作った RetryWatcherを使う方法とInformerを使う方法を試したが, Informerを使うほうがよさそう kubernetes-pod-observer\nつかうもの macOS Mojave 10.14 go version go1.14.6 darwin/amd64 kubernetes/client-go v0.19.2 Kubernetesクラスタ v1.15.12-gke.20 やったこと RetryWatcherでwatchする Informerを使ってみる RetryWatcherでwatchする PodをListするAPI1でwatchオプションを指定すると, Podの情報(event)がリアルタイムで流れてくる.\nこのeventを見ることで, Podが作成/更新/削除されたタイミングを把握することができる.\n# PodのList APIにwatchオプションをつけて実行(kubectlは内部でAPIを叩いている) $ kubectl get pods -w # この状態で別ターミナルからJobを実行して削除 $ kubectl create job busybox --image=busybox -- /bin/sh -c \u0026#39;echo hello\u0026#39; $ kubectl delete job busybox # kubectl get pods -w の出力 NAME READY STATUS RESTARTS AGE busybox-8kzpz 0/1 Pending 0 1s # Pod作成予約 busybox-8kzpz 0/1 Pending 0 1s # Pod作成 busybox-8kzpz 0/1 ContainerCreating 0 1s # コンテナ起動 busybox-8kzpz 0/1 Completed 0 3s # Pod(コンテナ)完了 busybox-8kzpz 0/1 Terminating 0 12s # 削除直前の状態 busybox-8kzpz 0/1 Terminating 0 12s # Pod削除完了 今回はGo純正のKubernetesクライアントライブラリclient-goを使い,\nwatch用のAPIを利用してPodをひたすら見張りつづけて状態変化に応じた処理を行うようなしくみを作ってみた.\n# 作業用ディレクトリとgo.modの作成 $ pwd /path/to/workspace $ mkdir kubernetes-pod-observer \u0026amp;\u0026amp; cd kubernetes-pod-observer $ go mod init github.com/uzimihsr/kubernetes-pod-observer $ touch observer.go observer.goはこんな感じで作った.\nKubernetes APIクライアントの認証情報はkubeconfig(~/.kube/config)を参照して,\nPodをwatchし, watchしたeventの内容を元に処理を分けるような形になっている.\nkubeconfig読み込みからクライアント立ち上げまでの流れは公式のサンプルコード2を参考にした.\n少しこだわったのはPodをwatchする際にRetryWatcher3を使用しているところ.\n普通にwatch用のAPIを叩く場合一定時間が経つとAPIサーバー側が接続を切ってしまう仕様なのだが45,\nこれを使うとwatchの接続が切れたときにクライアント側で勝手に再接続してくれる.\nまた, watchを再開するとすでに観測したことのあるeventも拾ってしまうので,\neventに紐づくResourceVersionを参照して既に見たことのあるeventは無視するようにした.\n実際にobserver.goが動いている状態で, Podを手で作ったり, Jobから作成してみる.\n# observer.goを実行 $ go run observer.go # 以下は別のターミナルから実行 # Podを作成(1) $ kubectl run busybox --image=busybox --restart=Never -- /bin/sh -c \u0026#39;echo hello\u0026#39; # Podを削除(2) $ kubectl delete pod busybox # Job経由でPodを作成(3) $ kubectl create job busybox --image=busybox -- /bin/sh -c \u0026#39;echo hello\u0026#39; # Jobを削除(4) $ kubectl delete job busybox このときのobserver.goの出力は次のようになる.\n# observer.goの出力 # (1) ResourceVersion: 60844265 the Pod \u0026lt; busybox \u0026gt; has been created. ResourceVersion: 60844266 the Pod \u0026lt; busybox \u0026gt;\u0026#39;s phase is \u0026lt; Pending \u0026gt;. ResourceVersion: 60844267 the Pod \u0026lt; busybox \u0026gt;\u0026#39;s phase is \u0026lt; Pending \u0026gt;. ResourceVersion: 60844271 the Pod \u0026lt; busybox \u0026gt;\u0026#39;s phase is \u0026lt; Succeeded \u0026gt;. # (2) ResourceVersion: 60844359 the Pod \u0026lt; busybox \u0026gt;\u0026#39;s phase is \u0026lt; Succeeded \u0026gt;. ResourceVersion: 60844360 the Pod \u0026lt; busybox \u0026gt; has been deleted. # (3) ResourceVersion: 60844936 the Pod \u0026lt; busybox-dx4f8 \u0026gt; has been created by the Job \u0026lt; busybox \u0026gt;. ResourceVersion: 60844937 the Pod \u0026lt; busybox-dx4f8 \u0026gt;\u0026#39;s phase is \u0026lt; Pending \u0026gt;. ResourceVersion: 60844940 the Pod \u0026lt; busybox-dx4f8 \u0026gt;\u0026#39;s phase is \u0026lt; Pending \u0026gt;. ResourceVersion: 60844948 the Pod \u0026lt; busybox-dx4f8 \u0026gt;\u0026#39;s phase is \u0026lt; Succeeded \u0026gt;. # (4) ResourceVersion: 60845063 the Pod \u0026lt; busybox-dx4f8 \u0026gt;\u0026#39;s phase is \u0026lt; Succeeded \u0026gt;. ResourceVersion: 60845064 the Pod \u0026lt; busybox-dx4f8 \u0026gt; has been deleted. こんな感じで, watchしたPodの状態に応じた処理を行うことができた.\nInformerを使ってみる \u0026hellip;実は同じようなことをする公式のサンプルコード6がある,\nこっちはeventをwatchする部分でRetryWatcherの代わりに, Informer7というやつを使っている.\nInformerはwatchに関する処理をさらに抽象化したものらしくて, watchが途切れたときに再度接続を復活させたり,\nwatchしたeventの種類で処理を分けたりとwatch対象のリソース(Pod)の状態変化に応じた処理がしやすくなっている. らしい.\n(observer.goでは自分で記述していたような処理も内部でやってくれている)\nサンプルコードを真似てobserver.goを書き直してみる.\nポイントはInformerの作成時にeventのType(ADDED, MODIFIED, DELETED)に応じた処理をそれぞれAddFunc, UpdateFunc, DeleteFuncで設定するところ.\nこうしておくことで, 対象のリソース(Pod)の状態が変化したときにそれに応じた処理を設定することができる.\n実際に動かしてみる.\n# observer.goを実行 $ go run observer.go # 以下は別のターミナルから実行 # Job経由でPodを作成(1) $ kubectl create job busybox --image=busybox -- /bin/sh -c \u0026#39;echo hello\u0026#39; # Jobを削除(2) $ kubectl delete job busybox # observer.goの出力 # (1) Pod \u0026lt;busybox-m5cd6\u0026gt; was added by Job \u0026lt;busybox\u0026gt;. Pod \u0026lt;busybox-m5cd6\u0026gt; is Pending phase. Pod \u0026lt;busybox-m5cd6\u0026gt; is Pending phase. Pod \u0026lt;busybox-m5cd6\u0026gt; is Succeeded phase. previous: Pending phase # (2) Pod \u0026lt;busybox-m5cd6\u0026gt; is Succeeded phase. Pod \u0026lt;busybox-m5cd6\u0026gt; was deleted. observer.goを起動した状態でJobを実行し, Podの作成, 状態変化, 削除のタイミングで任意の処理を行うことができた.\n今回はデフォルトNamespaceのPod名を標準出力しただけだが, 頑張れば特に重要なNamespaceのPodについては変化のタイミングで特定の相手に通知を送ったりとかもできるはず.\n今回はPodの状態変化を見張る部分とそれに応じた処理を1つにしてしまっているが, もちろんサンプルコード6のようにAddFuncなどでキューにデータを送り, 別のgo routineでキューからデータを取り出して処理することもできる. はず.\n(ここで力尽きたのでやめた)\nおわり Kubernetesクラスタの外からPodをwatchして, その状態変化に応じた任意の処理を行うやつを作った.\nRetryWatcherを使う方法とInformerを使う方法の2通りを試してみたけど, Informerを使うほうがwatchを行う部分について難しいことを考える必要がなくて楽な感じがした.\nどっちを使うべきか迷ったらInformerを使うほうがおすすめ?らしい8.\nInformerはカスタムコントローラとかでも使われているみたいなので9, 時間があったらもう少し勉強してみたい.\nおまけ https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#list-pod-v1-core\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/kubernetes/client-go/tree/master/examples/out-of-cluster-client-configuration\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://pkg.go.dev/k8s.io/client-go/tools/watch#RetryWatcher\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://kubernetes.io/docs/reference/using-api/api-concepts/#efficient-detection-of-changes\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/kubernetes/client-go/issues/623#issuecomment-506822043\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/kubernetes/client-go/tree/master/examples/workqueue\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://pkg.go.dev/k8s.io/client-go/tools/cache#NewInformer\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://stackoverflow.com/questions/59544139/kubernetes-client-go-watch-interface-vs-cache-newinformer-vs-cache-newsharedi\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://github.com/kubernetes/sample-controller/blob/master/docs/controller-client-go.md\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2020-09-30T10:23:13+09:00","image":"/post/2020-09-30-kubernetes-client-go-watch-pods/sotochan.jpg","permalink":"/post/2020-09-30-kubernetes-client-go-watch-pods/","title":"client-goを使ってKubernetesのPodをクラスタ外からwatchする"},{"content":"自作Exporter Prometheusでスクレイプする用のExporterを自作してみた.\nやったことのまとめ GoのPrometheusクライアント1を使って, 自分で設定したメトリクスを表示できるExporterを作成した 作成したExporterとPrometheusをDockerで動かし, スクレイプできることを確認した https://github.com/uzimihsr/example-exporter\nつかうもの macOS Mojave 10.14 go version go1.14.6 darwin/amd64 prometheus/client_golang v1.7.1 Docker Desktop for Mac 2.1.0.3 Docker version 19.03.2 docker-compose version 1.24.1 やったこと Exporterの作成 Prometheusでスクレイプ Exporterの作成 # 作業用ディレクトリとgo.modの作成 $ pwd /path/to/workspace $ mkdir example-exporter \u0026amp;\u0026amp; cd example-exporter $ go mod init github.com/uzimihsr/example-exporter go: creating new go.mod: module github.com/uzimihsr/example-exporter $ touch main.go main.goはこんな感じで作る.\n書き方はDocsの例2とPrometheusクライアントのサンプルコード3を参考にした.\nポイントは/metricsへのリクエストをハンドリングする処理と別にgo routineを使ってメトリクスを更新する処理を入れているところ.\nこうすることで各メトリクスが並行に更新され, リクエストが来るたびにメトリクスの最新の値を返すことができる.\n試しに動かしてみる.\n# exporterの実行 $ go run main.go ブラウザで http://localhost:2112/metrics を開く.\nこんな感じでPrometheusが読めるフォーマットのメトリクスが表示された.\nメトリクス定義に記述したメトリクス名, 説明文, ラベルが反映されていることがわかる.\n# HELP example_number Example Gauge # TYPE example_number gauge example_number{fuga=\u0026#34;fugafuga\u0026#34;} -0.0056473753420378525 # HELP example_total Example Counter # TYPE example_total counter example_total{hoge=\u0026#34;hogehoge\u0026#34;} 13 Prometheusでスクレイプ せっかくなので, Prometheusでスクレイプしてみる.\n今回は試しにDocker上でPrometheusのコンテナと今回作った example-exporter のコンテナを同時に動かす.\nexample-exporter 用のDockerfileの書き方はGoアプリをDockerのscratchイメージで動かすを参考にする.\nさらにPrometheusの設定ファイルprometheus.ymlと,\n2つのコンテナを動かすためにdocker-compose.ymlを作成する.\nこの状態でdocker-composeで2つのコンテナを起動する.\n# ディレクトリの状態 $ tree . . ├── Dockerfile ├── docker-compose.yml ├── go.mod ├── go.sum ├── main.go └── prometheus.yml # docker-composeでコンテナを起動 $ docker-compose up -d Creating network \u0026#34;example-exporter_default\u0026#34; with the default driver Creating example-exporter ... done Creating prometheus ... done コンテナが起動した状態で http://localhost:9090/graph をホストのブラウザで開くと, Prometheusの画面が表示される.\n無事に起動できていれば example-exporter がスクレイプできている.\n試しに example-exporter のメトリクス名でクエリを投げてみると, ちゃんと時系列の値が表示される.\nexample_total はmain.goで設定した通り30秒ごとに値が加算され,\nexample_number はmain.goで設定した値の変化の間隔(10秒)がPrometheusのscrape_interval(15秒)より短いために15秒ごとに値が変化している.\nやったぜ.\n自作のExporterをスクレイプして, 設定したとおりにメトリクスが変化していることが確認できた.\nおわり PrometheusでスクレイプできるかんたんなExporterを自作してみた.\nPrometheus自体がGoで書かれているだけあって, Goだとかなり楽に記述できた. と思う.\n特にgo routineのおかげでメトリクスとHTTPハンドラの並行処理が簡単に書けるのがいいと思った.\n自作のAPIとかにこんな感じでExporterを実装すれば監視がめちゃめちゃ楽になりそう.\nおまけ prometheus/client_golang\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nInstrumenting a Go application\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nclient_golang/examples\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2020-09-16T22:32:00+09:00","image":"/post/2020-09-17-prometheus-exporter/sotochan.jpg","permalink":"/post/2020-09-17-prometheus-exporter/","title":"GoでPrometheus用のExporterをつくる"},{"content":"Goでコマンドラインツール golangでCLIツールを作るときに引数とかサブコマンドをいい感じにしてくれるライブラリのcobraを試した.\nやったことのまとめ cobraを使ってコマンドラインであいさつするおもちゃgreetingをつくった.\n# hello $ greeting hello hoge fuga piyo --message \u0026#39;Nice to meet you!\u0026#39; Hello, hoge, fuga, and piyo! Nice to meet you! # こんにちは $ greeting hello 鈴木 --message \u0026#39;お会いできて嬉しいです!\u0026#39; -l ja 鈴木さん, こんにちは! お会いできて嬉しいです! # goodbye $ greeting goodbye hoge fuga piyo Goodbye, hoge, fuga, and piyo! # さようなら $ greeting goodbye A -l ja Aさん, さようなら! つかうもの macOS Mojave 10.14 go version go1.14.6 darwin/amd64 cobra v1.0.0 やったこと cobraのインストールとサンプルコードの作成 サブコマンドの実装 cobraのインストールとサンプルコードの作成 cobraはこれ自身がコマンドとしても提供されているので, まずはこれをインストールする.\n# cobraをインストール $ go get -u github.com/spf13/cobra/cobra $ which cobra /GOPATH/bin/cobra $ cobra Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application. Usage: cobra [command] Available Commands: add Add a command to a Cobra Application help Help about any command init Initialize a Cobra Application Flags: -a, --author string author name for copyright attribution (default \u0026#34;YOUR NAME\u0026#34;) --config string config file (default is $HOME/.cobra.yaml) -h, --help help for cobra -l, --license string name of license for the project --viper use Viper for configuration (default true) Use \u0026#34;cobra [command] --help\u0026#34; for more information about a command.\ncobra initを使うと動く状態のサンプルコードを生成してくれる.1\n昔は空のディレクトリじゃないとできなかったみたいだけど, 今は気にしなくて大丈夫.\n# 新規ディレクトリを作成+go.modを作成 $ mkdir -p greeting \u0026amp;\u0026amp; cd greeting $ go mod init github.com/uzimihsr/greeting go: creating new go.mod: module github.com/uzimihsr/greeting # サンプルコードの作成 $ cobra init --pkg-name github.com/uzimihsr/greeting Your Cobra application is ready at /path/to/greeting $ tree . . ├── LICENSE ├── cmd │ └── root.go ├── go.mod └── main.go 1 directory, 4 files 作成されたサンプルコードはこんな感じ.\n試しに動かしてみる.\n# アプリの実行 $ go run main.go A longer description that spans multiple lines and likely contains examples and usage of using your application. For example: Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application. # root.goで設定されたヘルプメッセージ ちゃんと動いた.\nさらに, cobra addでサブコマンドを作成できる.\n追加したサブコマンドのファイルはroot.goと同じくcmd配下に作成される.\n# サブコマンド(hello)の作成/実行 $ cobra add hello hello created at /Users/uzimihsr/Workspace/greeting $ ls cmd hello.go root.go $ go run main.go hello hello called $ go run main.go hello -h A longer description that spans multiple lines and likely contains examples and usage of using your command. For example: Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application. Usage: greeting hello [flags] Flags: -h, --help help for hello Global Flags: --config string config file (default is $HOME/.greeting.yaml) サブコマンドの実装 あとはこのルートコマンド(root.go)とサブコマンド(hello.go)をいじっていく.\nどちらもinit()関数で引数やコマンドに必要な変数の設定を行い,\nCommand.Runのfunc()で実際に行う処理を記述しているので, この形式に従っていろいろいじってみる.\n最終的なディレクトリ構造はこんな感じ.\n出会いと別れの挨拶をするサブコマンドcmd/hello.goとcmd/goodbye.goを実装した.\n$ tree . . ├── cmd │ ├── goodbye.go │ ├── hello.go │ └── root.go ├── go.mod ├── go.sum └── main.go 1 directory, 6 files 実際に動かしてみる.\ngo installするとPATHが通ってすぐ使えるようになるのでべんり.\n# ビルドして実行 $ go build $ ./greeting hello World Hello, World! # GOPATHが通っている場合はinstallするとパスが通る $ go install $ which greeting $GOPATH/bin/greeting # -l で言語(en/ja)の指定ができる $ greeting goodbye A B -l ja Aさん, Bさん, さようなら! # --message で追加のメッセージを指定できる $ greeting hello A B C --message \u0026#39;Nice to meet you!\u0026#39; Hello, A, B, and C! Nice to meet you! # goodbyeには敢えて --message のflagをつけていないので指定するとエラーになる $ greeting goodbye A --message \u0026#39;See you again!\u0026#39; Error: unknown flag: --message Usage: greeting goodbye [NAME] [flags] Flags: -h, --help help for goodbye Global Flags: -l, --lang string Language : en, ja (default \u0026#34;en\u0026#34;) unknown flag: --message # 引数の数が不正でもエラーになる $ greeting hello Error: requires at least 1 arg(s), only received 0 Usage: greeting hello [NAME] [flags] Flags: -h, --help help for hello -m, --message string Help message for toggle Global Flags: -l, --lang string Language : en, ja (default \u0026#34;en\u0026#34;) requires at least 1 arg(s), only received 0 やったぜ.\n機能はしょぼいけど, サブコマンドごとに違う機能を持つCLIツールができた.\nおわり Cobraを使って簡単なCLIツールを作った.\nサブコマンドごとにflag(オプション)が管理できたり, Argsで引数の条件を指定できたりするのがいいと思う.\n今までGoでCLIを作るときは標準パッケージのflagを使ってたけど,\nこれからはCobraでサクッと作るようにしたい.\nおまけ Cobra Generator\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2020-09-03T22:10:37+09:00","image":"/post/2020-09-03-golang-cobra/sotochan.jpg","permalink":"/post/2020-09-03-golang-cobra/","title":"cobraでかんたんなCLIツールをつくった"},{"content":"大騒ぎ 8月のそとちゃんはたいへんだった.\nまとめ 誤飲事件 ひなたぼっこを満喫 カリカリ問題の進捗 誤飲事件 なんと言ってもヒモ誤飲事件があった.\nやばいことにならなくて本当によかった\nしもべの不注意が原因なので反省します pic.twitter.com/tsGqCQD8Q0\n\u0026mdash; ずみし (@uzimihsr) August 6, 2020 もともとそんなにおもちゃを咥えたりしないかしこいねこだったんだけど,\nその日はおもちゃにまたたびをかけてたので興奮して食べちゃったんじゃないかと推測している.\nというわけでまたたびは当分の間禁止になった(かわいそう).\nきげんわるい pic.twitter.com/nkanUTcHit\n\u0026mdash; ずみし (@uzimihsr) August 29, 2020 また, 今回の件でやっぱりペット保険に入ろうと思ったので, 診察ついでに獣医さんにそとちゃんのだいたいの年齢を調べてもらった.\n(元野良とかで正確な生年月日がわからないねこは獣医さんに推測してもらう必要がある)\n歯の生え方とかからだいたい推測できるらしい.\n2016年5月生まれ(推定). やっぱり4さいくらいだった.\nうちに来たのが5月1日なので,\n記念日も兼ねて 2016年5月1日生まれ ということにする.\n知らなかった #世界猫の日 pic.twitter.com/40M6d3G8IY\n\u0026mdash; ずみし (@uzimihsr) August 8, 2020 やっぱり4さいのねこは威厳がありますね\u0026hellip;(ほんまか?)\nひなたぼっこを満喫 8月は天気のいい日が多くて, ひなたぼっこが捗ったらしい.\nとくに窓際のダンボールとハンモックがお気にいりだった.\nhttps://twitter.com/uzimihsr/status/1290172623884705793\nひなたぼっこ pic.twitter.com/lJdQchyKDA\n\u0026mdash; ずみし (@uzimihsr) August 20, 2020 あとはベランダで力尽きたセミの観察にも熱心だった.\nベランダを監視 pic.twitter.com/NJD1eaGO2S\n\u0026mdash; ずみし (@uzimihsr) August 17, 2020 陽に当たるのは健康にもいいはずなんだけど,\nそのまま寝落ちするのは熱中症が心配なのでやめてほしい\u0026hellip;\nまーた直射日光浴びてる pic.twitter.com/z6NQd2fgi8\n\u0026mdash; ずみし (@uzimihsr) August 2, 2020 すやすや pic.twitter.com/YGmSbf1HC0\n\u0026mdash; ずみし (@uzimihsr) August 5, 2020 カリカリ問題の進捗 カリカリ食べない問題がちょっとだけ改善?した.\n俺がいない間ならカリカリをちゃんと食べるのがよくわかったので,\n今月は夕方にときどき外出するようにしてみた.\n割と効果はあって, 午後に出しているカリカリを残さず食べるようになった.\n←しもべが出かける前 帰宅時(2h後)→ pic.twitter.com/Bajm6CzAoO\n\u0026mdash; ずみし (@uzimihsr) August 15, 2020 さらに何日か繰り返すと夕方にカリカリを食べるのが習慣化されたみたいで,\n俺が家にいても半分くらいは食べてくれるようになった.\nえらい.🎉\nちゃんとカリカリをたべられるねこはかっこいいですね.\nおわり 8月のそとちゃんは大変なこともあったけどやっぱりだいたい元気だった.\n9月はちょっと大変な予定があるので, なるべくそとちゃんに負担をかけずに終わらせられるようにしたい\u0026hellip;\nおまけ ","date":"2020-09-02T21:28:29+09:00","image":"/post/2020-09-02-sotochan/sotochan03.jpg","permalink":"/post/2020-09-02-sotochan/","title":"8月のそとちゃんまとめ(2020)"},{"content":"Kubernetes完全に理解した() CKADを受験したので受験記的なやつ. 個人の感想. n番煎じ.\nCKAD受験の流れ 受験の経緯 試験対策 申し込み 試験当日 試験について 結果 受験の経緯 CKADはKubernetesクラスタでアプリを開発したり動かしたりする能力を証明する公式の認定試験.\n他にもクラスタ管理者向けのCKAっていう試験もある.\n去年の10月くらいから業務でKubernetesを半年くらい使っていてだんだん勉強のモチベーションが保てなくなってきたので, 目先の目標として受験することにした.\n試験対策 7月に受験を決めてから1ヶ月の間, 普段の業務の他に以下の教材を使って試験対策をした.\nKubernetes完全ガイド\n基本はこれ.\nPodとかDeploymentとかの基本的なk8sリソースについてしっかり勉強できる.\n一通り読んだけど, 11章以降のクラスタ自体の操作とかの内容はあんまり試験に出なかったように思う. 13章(セキュリティ)の内容はちょっとだけ出たかも.\n最近第2版が出たらしい. 試験に関係なく持っておくと役に立つ.\nCKAD-exercises\nCKADの問題を解くための基本的なリソース操作などの練習問題集.\n実際の問題とは少し違うけど, 基本操作はこれで一通り練習できる.\n2周くらいやってコツをつかんだ.\nA Cloud GuruのCKADコース\n本番の対策用. 有料(7日間無料).\nHANDS-ON-LAB, 特に Practice Exam の問題は本番の問題にけっこう近かったように感じる.\nKubernetes完全ガイドを読んでいるのでハンズオン以外の内容は飛ばしても問題なかった.\n1週間だけ無料なので, 試験直前に登録して終わったあとにすぐ解約した.\n申し込み 英語イキリオタクだけど母語は日本語なので, できるだけ合格の確率を上げるために日本語で受験できるという触れ込みのCKAD-JPを受けることにした.\n\u0026hellip;が, 後述のように実際は申し込みの手順も試験官とのやり取りも全部英語だったので普通のCKADを受けたほうがよかったのかも.\n申し込みにはLinux Foundationとpsiのアカウントが必要になるけど, 1つのGoogleアカウントで全部登録できるのでかんたんだった.\nやることは名前とかの情報を入力して, クレカで受験料を払って, 試験日を予約するだけ.\n試験日の予約は結構空きが少なかったので, もっと余裕を持って予約すればよかった.\n受験用のブラウザはChromeがおすすめで, 事前に拡張機能をインストールしておく必要がある.\nさらに念の為にCompatibility Checkをやっておくと安心.\n試験当日 試験は自分の部屋で受けた.\n試験開始の15分前くらいにmy portalで 試験開始 的なボタンを押すと勝手に画面が切り替わって, 試験官がライブチャットで以下の内容を指示してきたのでその通りにした.\n拡張機能を使って画面とカメラ, マイクを共有しろ(顔を映せ) 使ってるOSを教えろ 強制終了メニュー(⌘+⌥+Esc)を表示して起動中のアプリを見せろ(ブラウザ以外消せ) カメラ越しに身分証(パスポートとクレジットカード)を見せろ 机の上のものを全部片付けろ カメラを持ったまま部屋を一周しろ 試験中はカメラに顔が映るようにして目線を画面から外すな 要はオンライン試験なのでカンニングができないように注意しろってことだと思う.\n特に片付けのタイミングで, \u0026ldquo;鼻をかむための箱ティッシュは試験中使えないよ!\u0026rdquo; って言われたのが面白かった.\n鼻炎だから無いと辛かったんだけどカンニング対策なので許されず, 最終的に無地のハンカチを胸ポケットに入れて使うことになった.\nちなみに, CKAD-JPで受験したのになぜか試験官とのやりとりは最後までずっと英語だった.\nとはいえ音声会話ではなくテキスト形式なのでそんなに困ることもなかった.\n最初の試験官とのやりとりが終わったあとはすぐに試験問題の画面に切り替わるので, あとは問題を解くだけだった.\n残り時間が15分になったときと試験終了時に再度試験官とやりとりが発生したけど, 特に何もなくおわった.\n試験について 具体的な問題の内容を公開するのは許されていないので, 簡単なメモ.\n試験用の画面はこんな感じ\n左側に問題, 右側にターミナルの画面. Katacodaっぽさがある.\nKubernetesクラスタとかkubectlはすでにセットアップされていて, 問題の内容をコマンドラインでガンガン解いていく感じ.\ntmuxが使えるので, 自分はターミナルの画面を左右分割して片方でkubectl -hとかkubectl explainの内容を表示しながらもう片方で操作するようにした.\nこの画面以外にも1タブだけ公式Docsを開くことが許されてたけど, そこまで使わなかった. というかDocsをちんたら調べてる暇はなかった.\n試験時間は2時間で問題数は19問\n\u0026quot;~のimageを使ったPodを作れ. それをXX番ポートで公開するServiceを作れ\u0026quot; とか,\n\u0026quot;~のnamespaceに壊れているPodがあるのでそれを探して修復しろ\u0026quot; 的な問題が立て続けに来た.\n問題を解く順序は決められていないので, 飛ばした問題をあとから解き直したりもできた.\n問題文は英語, 日本語, 中国語が自由に切り替えられる\n\u0026hellip;が, 日本語の翻訳が割とガバめなのと普段英語で覚えてる用語がカタカナになっただけでかなり混乱したので, 結局最後まで英語で解いた.\nスピード勝負\n約20問を2時間, 平均で約6分/1問のペースで解かなければならなかった.\nいちいちYAMLを0から手で書いていては到底間に合わない ので, kubectlでサクッと作るか, 公式Docsからコピペするのが良さげ.\nすでに存在するリソースを修正するような問題も出るので, kubectl editやkubectl get -o yamlとかも使う必要があった.\nまさに YAML地獄.\nnamespaceが指定されている問題とそうでない問題がある\nこれは結構重要.\nリソースを操作するnamespaceが指定されている問題がある一方, 何の指定もない問題もあったのでちょっとだけ混乱した.\n何も指定されていない場合はdefaultで操作したけど, 結果を見るにそれでよかったっぽい.\n複数のクラスタを操作する必要がある\nこれも重要だけど, どの問題も最初にcontextを切り替えるコマンドが指定されているので忘れずコピペすればそこまで問題にはならなかった.\n結果 結局時間はギリギリで, 最後の19問目を解いている途中で時間切れになった.\n結果は試験終了後36時間以内に届くことになっていたが, 結構待たされて34時間後にメールで結果が届いた.\n結果は\u0026hellip;?\n合格だった. 🎉\n合格点66点に対して得点は91点だったので, そこそこ余裕を持って合格できたんじゃなかろうか.\nたぶん100点満点なので解ききらなかった最後の1問以外はほとんど正解. だったらいいな.\nおわり というわけでCKADに合格した.\n合格したから会社の給料が上がるとか特にそういったことはないけど, なんかうれしい.\nとりあえずYAML耐性は大幅に上がったはず.\nようやくKubernetes完全に理解した()のでこの勢いで次はCKAも獲りたいけど, もっと勉強する必要がありそう\u0026hellip;\nおまけ ","date":"2020-08-17T21:35:13+09:00","image":"/post/2020-08-17-ckad/sotochan.jpg","permalink":"/post/2020-08-17-ckad/","title":"CKAD-JPを受験した"},{"content":"肝を冷やした 結果的に問題はなかったけど, 不注意でそとちゃんを危険な目に遭わせてしまったので自分への戒めとして記録しておく.\nまとめ 今回の教訓.\nねこがヒモ付きのおもちゃで遊んでるときは目を離しちゃだめ ヒモの誤飲は最悪死に至るほど危険 誤飲の疑いがある時点で急患で病院に行く, 様子見は危険 誤飲してから検査までの間は水とごく少量のウェットフードだけ食べさせる おもちゃのヒモが消えた ある日の夜のこと.\nヒモ付きのボールのおもちゃ\nあたらしいおもちゃ pic.twitter.com/EqbMGAtRMg\n\u0026mdash; ずみし (@uzimihsr) June 26, 2020 でねこをひとりで遊ばせていたところ, 突然ヒモが無くなっていたことに気づいた.\n遊んでて抜けたのか, ヒモが付いてた場所にぽっかり穴が空いている.\nまわりを探しても見つからなかったので, ねこが食べたかもしれない.\nそとちゃん「みゃーん」\nそとちゃん「(ごろーん)」 でもなんかねこは元気っぽい.\nもしかして, 食べてない? ヒモはどこか見つけづらいところに入ってしまっただけ?\nとりあえず一晩様子を見て, 朝になってから動物病院にいくことにした.\n(今だから言えるけどこの判断は危険だった. 本当は急患で行くべきだった.)\n急変 夜中に容態が急変することはなく, 朝になったので速攻で診察予約の電話をかける.\n電話してる途中にいきなり,\nそとちゃん「ぐっ, くぽっ」\nそとちゃん「げっ, ぐげっ, がっ」\nなんか吐いた!!!\nやっぱりヒモを食べていた\u0026hellip;\n全部吐き出せたので最悪の事態は避けられたっぽい.\n獣医さんに電話で相談.\nこの日は朝から診察の予約が埋まっていて, 異物が全部吐き出せていて急患じゃないのなら夕方に診てくれるとのこと.\nさらにそれまでにやるべきことも教えてもらえた.\n内蔵が傷ついているかもしれないので基本的に水以外は与えないでおく 吐き出してからある程度時間が経ってから少しだけウェットフードを食べさせて様子を見る もしフードを吐いたり容態が急変したら急患ですぐ病院に行く 言われたとおりにして様子を見て, ねこも元気なままだったので少し安心した.\nおもちゃの紐食べてゲロ吐いたのにかっこつけてるねこ\n病院行ってきます pic.twitter.com/592Av5ytND\n\u0026mdash; ずみし (@uzimihsr) August 5, 2020 検査結果 夕方に動物病院へ行き, X線検査と超音波検査をしてもらった.\n幸いなことに内蔵に問題は見つからなかった.\nめっちゃ安心した.\nと同時に, 先生にめっちゃ怒られた.\n15cmくらいの毛糸状のヒモは誤飲すると本当に危険, 死ぬこともある ヒモ付きのおもちゃで遊んでる間は目を離してはいけない 誤飲の疑いがある時点で急患で動物病院に行くべき そのとおり. 完全に俺の不注意が原因だった.\n普段はおもちゃをくわえたりしないねこだけど, この日はまたたびで興奮していたのでもっと注意すべきだった.\n反省しかない.\nちなみに俺が怒られてる横でもそとちゃんは診察台でごろごろして余裕ぶっこき丸だった.\nやっぱり病院は嫌いじゃないみたい.\nおわり ちなみに今回誤飲して吐き出したヒモはこんな感じ.\nこんな異物がねこのお腹の中にあったと思うとゾッとする.\n今回は運良く全部吐き出せただけで, 最悪の事態だってありえた.\n二度とこんな事が起こらないようにしたい.\nおまけ ","date":"2020-08-13T22:55:13+09:00","image":"/post/2020-08-13-sotochan/sotochan04.jpg","permalink":"/post/2020-08-13-sotochan/","title":"ねこがおもちゃのヒモを誤飲した話"},{"content":"クエリ1個で済ませたかった SELECT文のWHEREで絞りたい条件が複数あるときに静的なクエリでできないか試したけど, あんまり良くなさそうだった.\nまとめ ORとかCASEとかを使うと一応動くものは作れる.\nけどあんまりパフォーマンスが良くなさそうなのでおとなしく動的SQLを使うほうが良さげ.\n# どちらもTEAMとROLEに空文字(\u0026#39;\u0026#39;)以外の文字列が指定された場合のみWHERE句が有効になる mysql\u0026gt; SELECT * FROM employee -\u0026gt; WHERE (team = @TEAM OR @TEAM = \u0026#39;\u0026#39;) -\u0026gt; AND (role = @ROLE OR @ROLE = \u0026#39;\u0026#39;) -\u0026gt; ; mysql\u0026gt; SELECT * FROM employee -\u0026gt; WHERE CASE WHEN @TEAM = \u0026#39;\u0026#39; THEN @TEAM ELSE team END = @TEAM -\u0026gt; AND CASE WHEN @ROLE = \u0026#39;\u0026#39; THEN @ROLE ELSE role END = @ROLE -\u0026gt; ;\n環境 macOS Mojave 10.14 MySQL 5.7.25 やりかた まずは適当なテーブルを作成する.\nmysql\u0026gt; source employee.sql mysql\u0026gt; DESC employee; +-------+-------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | name | varchar(10) | YES | | NULL | | | team | varchar(10) | YES | | NULL | | | role | varchar(10) | YES | | NULL | | | age | int(11) | YES | | NULL | | +-------+-------------+------+-----+---------+----------------+ 5 rows in set (0.00 sec) mysql\u0026gt; SELECT * FROM employee; +----+----------+------+---------+------+ | id | name | team | role | age | +----+----------+------+---------+------+ | 1 | Alice | A | manager | 30 | | 2 | Ben | B | manager | 50 | | 3 | Charlie | A | member | 40 | | 4 | Daniel | A | member | 30 | | 5 | Emily | A | member | 20 | | 6 | Florence | A | member | 30 | | 7 | George | A | trainee | 20 | | 8 | Harry | B | member | 40 | | 9 | Isabel | B | member | 40 | | 10 | Jack | B | trainee | 20 | | 11 | Katie | B | trainee | 20 | +----+----------+------+---------+------+ 11 rows in set (0.00 sec)\nこのテーブルから「teamがAで, かつroleがmember」のレコードを抽出するクエリは次のように書ける.\nmysql\u0026gt; SELECT * FROM employee WHERE team = \u0026#39;A\u0026#39; AND role = \u0026#39;member\u0026#39;; +----+----------+------+--------+------+ | id | name | team | role | age | +----+----------+------+--------+------+ | 3 | Charlie | A | member | 40 | | 4 | Daniel | A | member | 30 | | 5 | Emily | A | member | 20 | | 6 | Florence | A | member | 30 | +----+----------+------+--------+------+ 4 rows in set (0.00 sec)\nここまではいいんだけど,\n外部からクエリを投げるときなんかにteamとroleがそれぞれ指定されたときだけWHEREの条件を増やして, 次のようなクエリを自動で作れないか考えてみる.\n# roleだけ指定された場合 mysql\u0026gt; SELECT * FROM employee WHERE role = \u0026#39;member\u0026#39;; # teamだけ指定された場合 mysql\u0026gt; SELECT * FROM employee WHERE team = \u0026#39;A\u0026#39;; # 両方指定された場合 mysql\u0026gt; SELECT * FROM employee WHERE team = \u0026#39;A\u0026#39; AND role = \u0026#39;member\u0026#39;; # 何も指定されなかった場合 mysql\u0026gt; SELECT * FROM employee; これをプログラム側で実現するなら次のように動的にクエリを組み立てればできる.\n(これは簡単すぎる例, 本当はこんな適当な文字列連結でクエリを組み立てるのはSQLインジェクションとかを考えるとたぶんセキュリティ的によろしくない)\nsql_example.go\nこれでも問題なさそうなんだけど,\nプログラム側の処理に頼らずSQLだけでこんな感じのクエリを組み立てたくなった.\nできそうなのは2通り.\nORを使う レコードの要素にNULLがない場合はORで複数の条件を合わせればできそう.\nmysql\u0026gt; SELECT * FROM employee -\u0026gt; WHERE (team = @TEAM OR @TEAM = \u0026#39;\u0026#39;) -\u0026gt; AND (role = @ROLE OR @ROLE = \u0026#39;\u0026#39;) -\u0026gt; ; WHERE (team = @TEAM OR @TEAM = '')はパラメータ@TEAMの内容によって次のように変わる.\n@TEAM = ''の場合\nWHERE (team = '' OR '' = '')\n-\u0026gt; team = ''となるレコードはないので'' = ''を満たすレコード(即ち全件)を抽出\n@TEAM = 'A'の場合\nWHERE (team = 'A' OR 'A' = '')\n-\u0026gt; 'A' = ''となるレコードはないのでteam = 'A'を満たすレコードのみ抽出\n実際に試してみる.\nmysql\u0026gt; SET @TEAM=\u0026#39;\u0026#39;; SET @ROLE=\u0026#39;\u0026#39;; mysql\u0026gt; SELECT * FROM employee WHERE (team = @TEAM OR @TEAM = \u0026#39;\u0026#39;) AND (role = @ROLE OR @ROLE = \u0026#39;\u0026#39;); +----+----------+------+---------+------+ | id | name | team | role | age | +----+----------+------+---------+------+ | 1 | Alice | A | manager | 30 | | 2 | Ben | B | manager | 50 | | 3 | Charlie | A | member | 40 | | 4 | Daniel | A | member | 30 | | 5 | Emily | A | member | 20 | | 6 | Florence | A | member | 30 | | 7 | George | A | trainee | 20 | | 8 | Harry | B | member | 40 | | 9 | Isabel | B | member | 40 | | 10 | Jack | B | trainee | 20 | | 11 | Katie | B | trainee | 20 | +----+----------+------+---------+------+ 11 rows in set (0.00 sec) # SELECT * FROM employee; と同じ mysql\u0026gt; SET @TEAM=\u0026#39;A\u0026#39;; SET @ROLE=\u0026#39;\u0026#39;; mysql\u0026gt; SELECT * FROM employee WHERE (team = @TEAM OR @TEAM = \u0026#39;\u0026#39;) AND (role = @ROLE OR @ROLE = \u0026#39;\u0026#39;); +----+----------+------+---------+------+ | id | name | team | role | age | +----+----------+------+---------+------+ | 1 | Alice | A | manager | 30 | | 3 | Charlie | A | member | 40 | | 4 | Daniel | A | member | 30 | | 5 | Emily | A | member | 20 | | 6 | Florence | A | member | 30 | | 7 | George | A | trainee | 20 | +----+----------+------+---------+------+ 6 rows in set (0.00 sec) # SELECT * FROM employee WHERE team = \u0026#39;A\u0026#39;; と同じ mysql\u0026gt; SET @TEAM=\u0026#39;\u0026#39;; SET @ROLE=\u0026#39;member\u0026#39;; mysql\u0026gt; SELECT * FROM employee WHERE (team = @TEAM OR @TEAM = \u0026#39;\u0026#39;) AND (role = @ROLE OR @ROLE = \u0026#39;\u0026#39;); +----+----------+------+--------+------+ | id | name | team | role | age | +----+----------+------+--------+------+ | 3 | Charlie | A | member | 40 | | 4 | Daniel | A | member | 30 | | 5 | Emily | A | member | 20 | | 6 | Florence | A | member | 30 | | 8 | Harry | B | member | 40 | | 9 | Isabel | B | member | 40 | +----+----------+------+--------+------+ 6 rows in set (0.00 sec) # SELECT * FROM employee WHERE role = \u0026#39;member\u0026#39;; と同じ mysql\u0026gt; SET @TEAM=\u0026#39;A\u0026#39;; SET @ROLE=\u0026#39;member\u0026#39;; mysql\u0026gt; SELECT * FROM employee WHERE (team = @TEAM OR @TEAM = \u0026#39;\u0026#39;) AND (role = @ROLE OR @ROLE = \u0026#39;\u0026#39;); +----+----------+------+--------+------+ | id | name | team | role | age | +----+----------+------+--------+------+ | 3 | Charlie | A | member | 40 | | 4 | Daniel | A | member | 30 | | 5 | Emily | A | member | 20 | | 6 | Florence | A | member | 30 | +----+----------+------+--------+------+ 4 rows in set (0.00 sec) # SELECT * FROM employee WHERE team = \u0026#39;A\u0026#39; AND role = \u0026#39;member\u0026#39;; と同じ やったぜ.\nと思ったらこのやり方は個別の条件を指定するクエリに比べるとINDEXが使えなくなるので, 性能が良くないらしい\u0026hellip;1\n今はまだテーブルが小さいからうまくいってるように見えるだけなんだろうか.\nSQLチューニングはちゃんと勉強したことがないのでよくわからん.\nCASEでがんばる ちょっと探してみたらCASEを使う別の方法もあった2.\nmysql\u0026gt; SELECT * FROM employee -\u0026gt; WHERE CASE WHEN @TEAM = \u0026#39;\u0026#39; THEN @TEAM ELSE team END = @TEAM -\u0026gt; AND CASE WHEN @ROLE = \u0026#39;\u0026#39; THEN @ROLE ELSE role END = @ROLE -\u0026gt; ; こっちも構文が違うだけで結果は同じになるけど, パフォーマンスも変わらないんだろうか\u0026hellip;\nmysql\u0026gt; SET @TEAM=\u0026#39;A\u0026#39;; SET @ROLE=\u0026#39;member\u0026#39;; mysql\u0026gt; SELECT * FROM employee WHERE CASE WHEN @TEAM = \u0026#39;\u0026#39; THEN @TEAM ELSE team END = @TEAM AND CASE WHEN @ROLE = \u0026#39;\u0026#39; THEN @ROLE ELSE role END = @ROLE; +----+----------+------+--------+------+ | id | name | team | role | age | +----+----------+------+--------+------+ | 3 | Charlie | A | member | 40 | | 4 | Daniel | A | member | 30 | | 5 | Emily | A | member | 20 | | 6 | Florence | A | member | 30 | +----+----------+------+--------+------+ 4 rows in set (0.00 sec) # SELECT * FROM employee WHERE team = \u0026#39;A\u0026#39; AND role = \u0026#39;member\u0026#39;; と同じ mysql\u0026gt; SET @TEAM=\u0026#39;A\u0026#39;; SET @ROLE=\u0026#39;\u0026#39;; mysql\u0026gt; SELECT * FROM employee WHERE CASE WHEN @TEAM = \u0026#39;\u0026#39; THEN @TEAM ELSE team END = @TEAM AND CASE WHEN @ROLE = \u0026#39;\u0026#39; THEN @ROLE ELSE role END = @ROLE; +----+----------+------+---------+------+ | id | name | team | role | age | +----+----------+------+---------+------+ | 1 | Alice | A | manager | 30 | | 3 | Charlie | A | member | 40 | | 4 | Daniel | A | member | 30 | | 5 | Emily | A | member | 20 | | 6 | Florence | A | member | 30 | | 7 | George | A | trainee | 20 | +----+----------+------+---------+------+ 6 rows in set (0.00 sec) # SELECT * FROM employee WHERE team = \u0026#39;A\u0026#39;; と同じ mysql\u0026gt; SET @TEAM=\u0026#39;\u0026#39;; SET @ROLE=\u0026#39;member\u0026#39;; mysql\u0026gt; SELECT * FROM employee WHERE CASE WHEN @TEAM = \u0026#39;\u0026#39; THEN @TEAM ELSE team END = @TEAM AND CASE WHEN @ROLE = \u0026#39;\u0026#39; THEN @ROLE ELSE role END = @ROLE; +----+----------+------+--------+------+ | id | name | team | role | age | +----+----------+------+--------+------+ | 3 | Charlie | A | member | 40 | | 4 | Daniel | A | member | 30 | | 5 | Emily | A | member | 20 | | 6 | Florence | A | member | 30 | | 8 | Harry | B | member | 40 | | 9 | Isabel | B | member | 40 | +----+----------+------+--------+------+ 6 rows in set (0.00 sec) # SELECT * FROM employee WHERE role = \u0026#39;member\u0026#39;; と同じ mysql\u0026gt; SET @TEAM=\u0026#39;\u0026#39;; SET @ROLE=\u0026#39;\u0026#39;; mysql\u0026gt; SELECT * FROM employee WHERE CASE WHEN @TEAM = \u0026#39;\u0026#39; THEN @TEAM ELSE team END = @TEAM AND CASE WHEN @ROLE = \u0026#39;\u0026#39; THEN @ROLE ELSE role END = @ROLE; +----+----------+------+---------+------+ | id | name | team | role | age | +----+----------+------+---------+------+ | 1 | Alice | A | manager | 30 | | 2 | Ben | B | manager | 50 | | 3 | Charlie | A | member | 40 | | 4 | Daniel | A | member | 30 | | 5 | Emily | A | member | 20 | | 6 | Florence | A | member | 30 | | 7 | George | A | trainee | 20 | | 8 | Harry | B | member | 40 | | 9 | Isabel | B | member | 40 | | 10 | Jack | B | trainee | 20 | | 11 | Katie | B | trainee | 20 | +----+----------+------+---------+------+ 11 rows in set (0.00 sec) # SELECT * FROM employee; と同じ おわり 一応動くものは作れたけど, このやり方だとパフォーマンス面を考えると微妙みたい\u0026hellip;\n参考にした記事にもあるように, やっぱりクエリを変化させたいときは動的に組み立てるほうがいいんだろうか?\n目的の用途はそこまでスピードが求められるものではないので一旦はこれで良しとしたいけど, やっぱりちょっとひっかかる\u0026hellip;\nSQLの勉強は正直あまりやりたくないので多分ORMとか使って動的SQLを組み立てるほうが楽そう.\nおまけ SQLにおける条件付きのWHERE句- スマートなロジックを使わない場合\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n検索SQLで、検索欄が空欄のとき、全検索にする\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2020-08-05T20:55:20+09:00","image":"/post/2020-08-05-sql/sotochan.jpg","permalink":"/post/2020-08-05-sql/","title":"パラメータがあるときだけWHERE句が有効になるようなSQLを書こうとして詰んだ"},{"content":"割とごきげん 7月もそとちゃんは元気だった.\nまとめ ダンボールブーム再来 カリカリ食べない問題 歯みがきの進捗 ダンボールブーム再来 またダンボールが増えた📦\n新しいはこも気に入った pic.twitter.com/h5S6t7lLpg\n\u0026mdash; ずみし (@uzimihsr) July 4, 2020 今まで使ってたAmazonのダンボールより浅くて, フチによっかかったりできるのがお気に入りらしい.\nnyamazon pic.twitter.com/SlJKDmv2cn\n\u0026mdash; ずみし (@uzimihsr) July 2, 2020 あごのっけてるのかわいい pic.twitter.com/GPBwyDkHSb\n\u0026mdash; ずみし (@uzimihsr) July 6, 2020 夜~朝はだいたいこのダンボールで寝てる.\nおはようございます pic.twitter.com/mW6raTOkOT\n\u0026mdash; ずみし (@uzimihsr) July 9, 2020 昼間はいつも窓際のダンボールベッドで寝てる.\nダンボールに猫用ベッドを詰めただけなんだけどこれもかなりお気に入り.\nごめん寝的ななにか pic.twitter.com/7mm7cqs7yL\n\u0026mdash; ずみし (@uzimihsr) July 19, 2020 おててまくらにしてる pic.twitter.com/JARBjliSWk\n\u0026mdash; ずみし (@uzimihsr) July 22, 2020 なんでそんな窮屈な姿勢で寝るの？ pic.twitter.com/wrUa9hyEnZ\n\u0026mdash; ずみし (@uzimihsr) July 27, 2020 結論: そとちゃんは常に寝ている💤\nカリカリ食べない問題 そとちゃんがカリカリ食べない問題が発生した😭\nおるすばんのときはカリカリ一粒残らず綺麗に食べるのにしもべが家に居ると全く食べない\n猫缶を要求されている pic.twitter.com/4aV7VISHlR\n\u0026mdash; ずみし (@uzimihsr) July 14, 2020 食欲が無いとか味が気にいらないとかじゃなくて,\n俺が家にいる間だけ食べない. どうして😭\n朝と昼に自動給餌器でカリカリを出してるんだけど,\n俺が寝てる間に出る朝のカリカリはきれいに食べてたり,\n昼に出したカリカリでも俺が夕方に出かけて戻ってくると食べてたりする.\n直接あげてる晩ごはんの黒缶パウチとかおやつのにぼしは喜んで食べるので,\nどうやら俺がいるともっと美味しいウェットフードとかおやつがもらえると思ってるみたい\u0026hellip;\nカリカリ残しまくってるのにおなかすいたアピールしてくる\nちゃんとごはん食べないとおやつはあげません pic.twitter.com/RajRpJvc3G\n\u0026mdash; ずみし (@uzimihsr) July 3, 2020 そとちゃんは元々下痢気味で療法食のカリカリを食べさせてるので, ちゃんと食べてくれないと困る\u0026hellip;\n獣医さんに相談しようか\u0026hellip;\n歯みがきの進捗 歯みがき苦手問題は少しだけ改善した.\n歯みがきシートはあまりにも嫌がるので一旦やめて,\n歯みがきジェルを指に塗って直接磨くようにしたらおとなしく触らせてくれるようになった.\n匂いを嗅いでも嫌な顔しない.\nもうちょっと歯みがきジェルを使って慣れさせてから再度歯みがきシートに挑戦したい.\nおわり そとちゃんは7月も元気だった.\n元気だけどもごはんをちゃんとたべなかったりするので,\n8月はいろいろ試して改善できるようにしたい.\nおまけ ","date":"2020-08-03T21:03:58+09:00","image":"/post/2020-08-03-sotochan/sotochan02.jpg","permalink":"/post/2020-08-03-sotochan/","title":"7月のそとちゃんまとめ(2020)"},{"content":"YAMLめんどくさい CKAD対策でCKAD-exercisesの問題をやってみたんだけどいちいちYAMLを書くのがめんどくさすぎたのでメモ.\nまとめ kubectl runとkubectl createでだいたいのリソースは作れそう.\n# Pod $ kubectl run nginx --image=nginx --restart=Never # Deployment $ kubectl create deployment nginx --image=nginx # Job $ kubectl create job busybox --image=busybox -- /bin/sh -c \u0026#39;date; echo Hello\u0026#39; # CronJob $ kubectl create cronjob busybox --image=busybox --schedule=\u0026#34;*/10 * * * *\u0026#34; -- /bin/sh -c \u0026#39;date; echo Hello\u0026#39; # ConfigMap $ kubectl create configmap my-config --from-literal=key1=config1 --from-literal=key2=config2 # Secret $ kubectl create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret 環境 macOS Mojave 10.14 kubectl v1.14.6 Kubernetes v1.14.10-gke.36 やりかた Pod kubectl runのオプションで--restart=NeverをつけてあげるとPodが作成できる.\n# nginxのPodを作成 $ kubectl run nginx --image=nginx --restart=Never pod/nginx created Podと同時にService(ClusterIP)を作成することもできる.\nまた, 使い捨てのPodを作成することもできる.\n他のPodの疎通確認するときなんかに便利.\n# PodとServiceを同時に作成 $ kubectl run nginx --image=nginx --restart=Never --port=80 --expose service/nginx created pod/nginx created $ kubectl get service nginx NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx ClusterIP 10.4.0.85 \u0026lt;none\u0026gt; 80/TCP 14m # 使い捨てのbusyboxのPodでServiceの疎通確認をする $ kubectl run busybox --image=busybox --restart=Never --rm -it -- wget -O- 10.4.0.85:80 Connecting to 10.4.0.85:80 (10.4.0.85:80) writing to stdout \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;Welcome to nginx!\u0026lt;/title\u0026gt; ... written to stdout pod \u0026#34;busybox\u0026#34; deleted # 使い捨てのbusyboxのPodを作成してshに入る $ kubectl run busybox --image=busybox --restart=Never --rm -it -- /bin/sh / $ hostname busybox / $ # Ctrl+Dで抜ける pod \u0026#34;busybox\u0026#34; deleted 実際のPodはつくらずにマニフェストのYAMLだけ生成することもできる.\nとりあえず下書きだけでも用意してさらに詳細な設定を書きたいときとかに便利.\n# Podは作成せずYAMLだけを表示させる $ kubectl run nginx --image=nginx --restart=Never --port=80 --env=key1=value1 --limits=\u0026#39;cpu=200m,memory=512Mi\u0026#39; --labels=app=v1 -o yaml --dry-run apiVersion: v1 kind: Pod metadata: creationTimestamp: null labels: app: v1 name: nginx spec: containers: - env: - name: key1 value: value1 image: nginx name: nginx ports: - containerPort: 80 resources: limits: cpu: 200m memory: 512Mi dnsPolicy: ClusterFirst restartPolicy: Never status: {} Deployment Pod以外のリソースはkubectl createで作れる.\nkubectl runでも作れるんだけど, Deprecatedなのであんまり使わないほうがよさそう.\nkubectl createだと今の所レプリカ数を指定するオプションがないっぽいのが残念.\n# kubectl runでのDeployment作成はDeprecatedらしい $ kubectl run nginx --image=nginx kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead. deployment.apps/nginx created # Deploymentを作成 $ kubectl create deployment nginx --image=nginx deployment.apps/nginx created kubectl runと同様に実際のリソース作成はせずYAMLだけ生成することもできる.\nこっちのほうが出番が多そう.\n# Deploymentを作成せずYAMLを表示させる $ kubectl create deployment nginx --image=nginx -o yaml --dry-run 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: {} status: {} CronJob, Job CronJobとJobもDeploymentと同様に作成できる.\n# Jobの作成 $ kubectl create job busybox --image=busybox -- /bin/sh -c \u0026#39;date; echo Hello\u0026#39; $ kubectl get pods -l job-name=busybox NAME READY STATUS RESTARTS AGE busybox-rvsr9 0/1 Completed 0 34s $ kubectl logs busybox-rvsr9 Tue Jul 28 14:48:24 UTC 2020 Hello # CronJobの作成 $ kubectl create cronjob busybox --image=busybox --schedule=\u0026#34;*/10 * * * *\u0026#34; -- /bin/sh -c \u0026#39;date; echo Hello\u0026#39; また, すでに存在するCronJobからJob部分だけを抜き出して作成することもできる.\n# CronJobからJobを作成 $ kubectl create job busybox --from=cronjob/busybox -o yaml --dry-run apiVersion: batch/v1 kind: Job metadata: annotations: cronjob.kubernetes.io/instantiate: manual creationTimestamp: null name: busybox spec: template: metadata: creationTimestamp: null spec: containers: - command: - /bin/sh - -c - date; echo Hello image: busybox imagePullPolicy: Always name: busybox resources: {} terminationMessagePath: /dev/termination-log terminationMessagePolicy: File dnsPolicy: ClusterFirst restartPolicy: OnFailure schedulerName: default-scheduler securityContext: {} terminationGracePeriodSeconds: 30 status: {} ConfigMap, Secret ConfigMapやSecretもkubectl createのオプションにKey-Valueの情報を渡して作ることができる.\n# ConfigMapの作成 $ kubectl create configmap my-config --from-literal=key1=config1 --from-literal=key2=config2 $ kubectl get configmap my-config -o jsonpath={.data.key1} config1 # Secretの作成 $ kubectl create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret $ kubectl get secret my-secret -o jsonpath={.data.key1} | base64 -D supersecret おわり kubectl run/createでかんたんなリソースが作れることを確認できた. いちいちYAMLを書く必要がないので便利.\nさらに--dry-runと-o yamlを使うとYAMLの雛形を生成できるので, 細かい設定を書きたい場合も便利に使えそう.\nおまけ 参考 https://github.com/dgkanatsios/CKAD-exercises ","date":"2020-07-29T20:43:33+09:00","image":"/post/2020-07-29-kubectl-run-and-create/sotochan.jpg","permalink":"/post/2020-07-29-kubectl-run-and-create/","title":"YAMLを使わずにkubectl run/createでサクッとリソースを作る"},{"content":"ConfigMapとかSecretとか Kubernetes完全ガイドの続き.\nConfigMapとかSecretとか, Podから利用できるリソースの話.\n読んだもの Kubernetes完全ガイド 7章(Config \u0026amp; Storageリソース) 重要そうなところとかよく使いそうなところだけまとめる.\n読んだことのまとめ ConfigMap Secret Volume PersistentVolume PersistentVolumeClaim ConfigMap Podから利用できる情報をKey-Value形式で保持するリソース.\n平文ファイル, 直接入力, マニフェストファイル(YAML)の3種類の方法で作成できる.\n# 平文ファイルからConfigMapを作成 $ cat config.txt abc def ghi 1234 $ kubectl create configmap --save-config sample-configmap-01 --from-file=./config.txt configmap/sample-configmap-01 created # kubectlで値を直接入力してConfigMapを作成 $ kubectl create configmap --save-config web-config \\ --from-literal=connection.max=100 \\ --from-literal=connection.min=10 configmap/web-config created # マニフェストファイルから作成する $ kubectl apply -f sample-configmap.yaml configmap/sample-configmap created # 値がKey-Value形式で保存されている $ kubectl get configmap sample-configmap-01 -o json | jq \u0026#39;.data\u0026#39; { \u0026#34;config.txt\u0026#34;: \u0026#34;abc\\ndef\\nghi\\n1234\\n\u0026#34; } $ kubectl get configmap web-config -o json | jq \u0026#39;.data\u0026#39; { \u0026#34;connection.max\u0026#34;: \u0026#34;100\u0026#34;, \u0026#34;connection.min\u0026#34;: \u0026#34;10\u0026#34; } $ kubectl get configmap sample-configmap -o json | jq \u0026#39;.data\u0026#39; { \u0026#34;connection.max\u0026#34;: \u0026#34;100\u0026#34;, \u0026#34;connection.min\u0026#34;: \u0026#34;10\u0026#34;, \u0026#34;nginx.conf\u0026#34;: \u0026#34;user nginx;\\nworker_processes auto;\\nerror_log /var/log/nginx/error.log;\\npid /run/nginx.pid;\\n\u0026#34;, \u0026#34;sample.properties\u0026#34;: \u0026#34;property.1=value-1\\nproperty.2=value-2\\nproperty.3=value-3\\n\u0026#34;, \u0026#34;thread\u0026#34;: \u0026#34;16\u0026#34; } sample-configmap.yaml\n作成したConfigMapはPodから環境変数またはマウントしたファイルとして扱うことができる.\n# ConfigMap(sample-configmap)のValue(Key=connection.max)が環境変数CONNECTION_MAXとして利用されている $ kubectl get pod sample-configmap-single-env -o json | jq \u0026#39;.spec.containers[].env[]\u0026#39; { \u0026#34;name\u0026#34;: \u0026#34;CONNECTION_MAX\u0026#34;, \u0026#34;valueFrom\u0026#34;: { \u0026#34;configMapKeyRef\u0026#34;: { \u0026#34;key\u0026#34;: \u0026#34;connection.max\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;sample-configmap\u0026#34; } } } $ kubectl exec -it sample-configmap-single-env env | grep CONNECTION_MAX CONNECTION_MAX=100 # ConfigMap(sample-configmap)のKey-ValueがVolume(config-volume)として/configにマウントされている $ kubectl get pod sample-configmap-multi-volume -o json | jq \u0026#39;.spec.volumes[0]\u0026#39; { \u0026#34;configMap\u0026#34;: { \u0026#34;defaultMode\u0026#34;: 420, \u0026#34;name\u0026#34;: \u0026#34;sample-configmap\u0026#34; }, \u0026#34;name\u0026#34;: \u0026#34;config-volume\u0026#34; } $ kubectl get pod sample-configmap-multi-volume -o json | jq \u0026#39;.spec.containers[0].volumeMounts[0]\u0026#39; { \u0026#34;mountPath\u0026#34;: \u0026#34;/config\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;config-volume\u0026#34; } $ kubectl exec -it sample-configmap-multi-volume ls /config connection.max\tconnection.min\tnginx.conf sample.properties thread $ kubectl exec -it sample-configmap-multi-volume cat /config/nginx.conf user nginx; worker_processes auto; error_log /var/log/nginx/error.log; pid /run/nginx.pid; sample-configmap-single-env.yaml\nsample-configmap-multi-volume.yaml\nSecret ConfigMapとは異なり, 秘密情報を扱うためのリソース.\n対応するSecretを利用するPodがある場合のみetcdからNodeの一時的な領域(tmpfs)にKey-Valueのデータが送られるようになっているので, ConfigMapに比べて機密性が高い.\nまた, 安全のためにKey-ValueのValueがbase64エンコードされていて少し見えにくくなっている.\nOpaqueタイプのSecretは基本的にConfigMapと同じような使い方ができる.\n# 平文ファイルからSecretを作成 $ echo -n \u0026#34;root\u0026#34; \u0026gt; ./username $ echo -n \u0026#34;rootpassword\u0026#34; \u0026gt; ./password $ kubectl create secret generic --save-config sample-db-auth-from-file \\ --from-file=./username --from-file=./password secret/sample-db-auth-from-file created # envfileからSecretを作成 $ cat \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; \u0026gt; env-secret.txt username=root password=rootpassword EOF $ kubectl create secret generic --save-config sample-db-auth-from-env-file \\ --from-env-file ./env-secret.txt secret/sample-db-auth-from-env-file created # kubectlで値を直接入力してSecretを作成 $ kubectl create secret generic --save-config sample-db-auth-from-literal \\ --from-literal=username=root --from-literal=password=rootpassword secret/sample-db-auth-from-literal created # マニフェストファイルからSecretを作成 $ kubectl apply -f sample-db-auth.yaml secret/sample-db-auth created # describeしても見えない $ kubectl describe secret sample-db-auth-from-literal Name: sample-db-auth-from-literal Namespace: default Labels: \u0026lt;none\u0026gt; Annotations: Type: Opaque Data ==== password: 12 bytes username: 4 bytes # 中身はbase64エンコードされている $ kubectl get secret sample-db-auth-from-literal -o json | jq \u0026#39;.data\u0026#39; { \u0026#34;password\u0026#34;: \u0026#34;cm9vdHBhc3N3b3Jk\u0026#34;, \u0026#34;username\u0026#34;: \u0026#34;cm9vdA==\u0026#34; } $ kubectl get secret sample-db-auth-from-literal -o json | jq -r \u0026#39;.data.username\u0026#39; | base64 -D root $ kubectl get secret sample-db-auth-from-literal -o json | jq -r \u0026#39;.data.password\u0026#39; | base64 -D rootpassword\nsample-db-auth.yaml\nIngressなどからTLSに必要な証明書と秘密鍵を扱うためのkubernetes.io/tlsタイプのSecretもある.\n# 秘密鍵(tls.key)とオレオレ証明書(tls.crt)を同時に作成 $ openssl req -x509 -nodes -days 365 -newkey rsa:2048 \\ -keyout ./tls.key -out ./tls.crt -subj \u0026#34;/CN=sample1.example.com\u0026#34; Generating a 2048 bit RSA private key ......+++ .........................................................................................................................................................................................................+++ writing new private key to \u0026#39;./tls.key\u0026#39; ----- $ ls tls.crt tls.key # 秘密鍵と証明書からSecretを作成 $ kubectl create secret tls --save-config tls-sample --key ./tls.key --cert ./tls.crt secret/tls-sample created # 秘密鍵と証明書のpemをbase64エンコードしたものが登録されている $ kubectl get secret tls-sample -o json | jq \u0026#39;.data\u0026#39; { \u0026#34;tls.crt\u0026#34;: \u0026#34;LS0tLS...tLS0K\u0026#34;, \u0026#34;tls.key\u0026#34;: \u0026#34;LS0tLS...tLS0K\u0026#34; } $ kubectl get secret tls-sample -o json | jq -r .data\u0026#39;[\u0026#34;tls.crt\u0026#34;]\u0026#39; | base64 -D | openssl x509 -noout -text Certificate: Data: Version: 1 (0x0) Serial Number: 12508610311645361173 (0xad9782ce1368f415) Signature Algorithm: sha256WithRSAEncryption Issuer: CN=sample1.example.com Validity Not Before: Jul 8 14:54:37 2020 GMT Not After : Jul 8 14:54:37 2021 GMT Subject: CN=sample1.example.com ... DockerHubのプライベートリポジトリなど, 認証のかかったDockerレジストリからimageを取得するための認証情報を扱う場合はkubernetes.io/dockerconfigjsonタイプのSecretを使用する.\nPodから認証がかかったリポジトリのimageをpullするにはマニフェストファイルの.spec.imagePullSecretsにSecret名を指定する(例 : sample-pull-secret.yaml).\n# 認証情報からSecretを作成 $ kubectl create secret docker-registry --save-config sample-registry-auth \\ --docker-server=SERVER \\ --docker-username=USER \\ --docker-password=PASSWORD \\ --docker-email=EMAIL secret/sample-registry-auth created # dockercfg形式の認証情報が保存されている $ kubectl get secret sample-registry-auth -o json | jq -r .data\u0026#39;[\u0026#34;.dockerconfigjson\u0026#34;]\u0026#39; | base64 -D {\u0026#34;auths\u0026#34;:{\u0026#34;SERVER\u0026#34;:{\u0026#34;username\u0026#34;:\u0026#34;USER\u0026#34;,\u0026#34;password\u0026#34;:\u0026#34;PASSWORD\u0026#34;,\u0026#34;email\u0026#34;:\u0026#34;EMAIL\u0026#34;,\u0026#34;auth\u0026#34;:\u0026#34;VVNFUjpQQVNTV09SRA==\u0026#34;}}} 作成したSecretはPodから環境変数またはマウントしたファイルとして扱うことができる.\n# Secret(sample-db-auth)のValue(Key=username)が環境変数DB_USERNAMEとして利用されている $ kubectl get pod sample-secret-single-env -o json | jq \u0026#39;.spec.containers[].env[]\u0026#39; { \u0026#34;name\u0026#34;: \u0026#34;DB_USERNAME\u0026#34;, \u0026#34;valueFrom\u0026#34;: { \u0026#34;secretKeyRef\u0026#34;: { \u0026#34;key\u0026#34;: \u0026#34;username\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;sample-db-auth\u0026#34; } } } $ kubectl exec -it sample-secret-single-env env | grep DB_USERNAME DB_USERNAME=root # Secret(sample-db-auth)のKey-ValueがVolume(config-volume)として/configにマウントされている $ kubectl get pod sample-secret-multi-volume -o json | jq \u0026#39;.spec.volumes[0]\u0026#39; { \u0026#34;name\u0026#34;: \u0026#34;config-volume\u0026#34;, \u0026#34;secret\u0026#34;: { \u0026#34;defaultMode\u0026#34;: 420, \u0026#34;secretName\u0026#34;: \u0026#34;sample-db-auth\u0026#34; } } $ kubectl get pod sample-secret-multi-volume -o json | jq \u0026#39;.spec.containers[].volumeMounts[0]\u0026#39; { \u0026#34;mountPath\u0026#34;: \u0026#34;/config\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;config-volume\u0026#34; } $ kubectl exec -it sample-secret-multi-volume ls /config password username $ kubectl exec -it sample-secret-multi-volume cat /config/username root Volume Podからディスクを扱うためのリソース.\nいくつか種類があるが, どれもPod上に静的に領域を指定して使用する.\nemptyDirはPodに一時的なディスク領域を作成し, Podが終了すると同時に削除される.\nPod内の複数のコンテナ間でファイルを共有したりするのに使える.\n# emptyDirが /cache にマウントされている $ kubectl get pod sample-emptydir -o json | jq \u0026#39;.spec.volumes[0]\u0026#39; { \u0026#34;emptyDir\u0026#34;: {}, \u0026#34;name\u0026#34;: \u0026#34;cache-volume\u0026#34; } $ kubectl get pod sample-emptydir -o json | jq \u0026#39;.spec.containers[0].volumeMounts[0]\u0026#39; { \u0026#34;mountPath\u0026#34;: \u0026#34;/cache\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;cache-volume\u0026#34; } # 中身は空 $ kubectl exec -it sample-emptydir -- ls -la /cache total 8 drwxrwxrwx 2 root root 4096 Jul 9 14:26 . drwxr-xr-x 1 root root 4096 Jul 9 14:26 ..\nsample-emptydir.yaml\nhostPathはNode上の領域を指定してPodのコンテナにマウントする.\nNodeに直接影響を与えるので扱いには注意が必要.\n# Node(gke-k8s-01-pool-2-641104a4-7r06)の /etc が /srv としてPodにマウントされている $ kubectl get pod sample-hostpath -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES sample-hostpath 1/1 Running 0 3m22s 10.0.1.5 gke-k8s-01-pool-2-641104a4-7r06 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; $ kubectl get pod sample-hostpath -o json | jq \u0026#39;.spec.volumes[0]\u0026#39; { \u0026#34;hostPath\u0026#34;: { \u0026#34;path\u0026#34;: \u0026#34;/etc\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;DirectoryOrCreate\u0026#34; }, \u0026#34;name\u0026#34;: \u0026#34;hostpath-sample\u0026#34; } $ kubectl get pod sample-hostpath -o json | jq \u0026#39;.spec.containers[0].volumeMounts[0]\u0026#39; { \u0026#34;mountPath\u0026#34;: \u0026#34;/srv\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;hostpath-sample\u0026#34; } # マウントしたNodeの領域にアクセスする $ kubectl exec -it sample-hostpath cat /srv/os-release | grep PRETTY_NAME PRETTY_NAME=\u0026#34;Container-Optimized OS from Google\u0026#34; # NodeのOS # コンテナ自体の領域にアクセスする $ kubectl exec -it sample-hostpath cat /etc/os-release | grep PRETTY_NAME PRETTY_NAME=\u0026#34;Debian GNU/Linux 9 (stretch)\u0026#34; # コンテナのOS # 実際にNodeの同じ領域のファイルを確認する $ gcloud compute ssh gke-k8s-01-pool-2-641104a4-7r06 gke-k8s-01-pool-2-641104a4-7r06 ~ $ cat /etc/os-release | grep PRETTY_NAME PRETTY_NAME=\u0026#34;Container-Optimized OS from Google\u0026#34;\nsample-hostpath.yaml\ndownwardAPIはPodの情報をPod上の領域にファイルとして配置する.\n# /srv/podnameと/srv/cpu-requestにmetadata.nameとrequests.cpuの値がファイルとして配置されている $ kubectl get pod sample-downward-api -o json | jq \u0026#39;.spec.volumes[0]\u0026#39; { \u0026#34;downwardAPI\u0026#34;: { \u0026#34;defaultMode\u0026#34;: 420, \u0026#34;items\u0026#34;: [ { \u0026#34;fieldRef\u0026#34;: { \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;fieldPath\u0026#34;: \u0026#34;metadata.name\u0026#34; }, \u0026#34;path\u0026#34;: \u0026#34;podname\u0026#34; }, { \u0026#34;path\u0026#34;: \u0026#34;cpu-request\u0026#34;, \u0026#34;resourceFieldRef\u0026#34;: { \u0026#34;containerName\u0026#34;: \u0026#34;nginx-container\u0026#34;, \u0026#34;divisor\u0026#34;: \u0026#34;0\u0026#34;, \u0026#34;resource\u0026#34;: \u0026#34;requests.cpu\u0026#34; } } ] }, \u0026#34;name\u0026#34;: \u0026#34;downward-api-volume\u0026#34; } $ kubectl get pod sample-downward-api -o json | jq \u0026#39;.spec.containers[0].volumeMounts[0]\u0026#39; { \u0026#34;mountPath\u0026#34;: \u0026#34;/srv\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;downward-api-volume\u0026#34; } # ファイルを確認する $ kubectl exec -it sample-downward-api cat /srv/podname sample-downward-api $ kubectl exec -it sample-downward-api cat /srv/cpu-request 1\nsample-downward-api.yaml\nprojectedは複数のVolume, Secret, ConfigMapなどを1つのvolumeMountsにまとめる.\n# Secret(sample-db-auth), ConfigMap(sample-configmap), downwardAPIが /srv にまとめてマウントされている $ kubectl get secret sample-db-auth NAME TYPE DATA AGE sample-db-auth Opaque 2 24h $ kubectl get configmap sample-configmap NAME DATA AGE sample-configmap 5 12d $ kubectl get pod sample-projected -o yaml | yq read - \u0026#39;spec.volumes[0]\u0026#39; name: projected-volume projected: defaultMode: 420 sources: - secret: items: - key: username path: secret/username.txt name: sample-db-auth - configMap: items: - key: nginx.conf path: configmap/nginx.conf name: sample-configmap - downwardAPI: items: - fieldRef: apiVersion: v1 fieldPath: metadata.name path: podname $ kubectl get pod sample-projected -o yaml | yq read - \u0026#39;spec.containers[0].volumeMounts[0]\u0026#39; mountPath: /srv name: projected-volume # 中身を確認 $ kubectl exec -it sample-projected ls /srv configmap podname secret $ kubectl exec -it sample-projected cat /srv/secret/username.txt root $ kubectl exec -it sample-projected cat /srv/configmap/nginx.conf user nginx; worker_processes auto; error_log /var/log/nginx/error.log; pid /run/nginx.pid; $ kubectl exec -it sample-projected cat /srv/podname sample-projected\nsample-projected.yaml\nPersistentVolume 永続化領域を扱うためのVolumeで, 個別のリソースとして扱う.\nネットワーク経由でPod等から利用するため, アクセス可能な場所にディスクを用意する必要がある.\n(Ingressみたいな感じ?)\n# GCPでディスクを作成 $ gcloud compute disks create --size=10GB sample-gce-pv --zone us-central1-a NAME ZONE SIZE_GB TYPE STATUS sample-gce-pv us-central1-a 10 pd-standard READY # PersistentVolume(sample-pv)を作成 $ kubectl apply -f sample-pv.yaml persistentvolume/sample-pv created $ kubectl get pv sample-pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE sample-pv 10Gi RWO Retain Available manual 56s sample-pv.yaml\nPersistentVolumeClaim Pod等から利用できるPersistentVolumeを払い出すためのリソース.\nクラスタが認識しているPersistentVolumeの中でPersistentVolumeClaimの条件に合ったものがPodに接続される.\n# PersistentVolume(sample-pv)が存在する状態でPersistentVolumeClaim(sample-pvc)を作成 $ kubectl apply -f sample-pvc.yaml persistentvolumeclaim/sample-pvc created $ kubectl get pvc sample-pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE sample-pvc Bound sample-pv 10Gi RWO manual 26s # PersistentVolume(sample-pv)がsample-pvcに確保(Bound)されている $ kubectl get pv sample-pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE sample-pv 10Gi RWO Retain Bound default/sample-pvc manual 111s # Podの/usr/share/nginx/htmlにsample-pvcで払い出されたPersistentVolumeがマウントされている $ kubectl get pod sample-pvc-pod -o yaml | yq read - \u0026#39;spec.volumes[0]\u0026#39; name: nginx-pvc persistentVolumeClaim: claimName: sample-pvc $ kubectl get pod sample-pvc-pod -o yaml | yq read - \u0026#39;spec.containers[0].volumeMounts[0]\u0026#39; mountPath: /usr/share/nginx/html name: nginx-pvc sample-pvc.yaml\nsample-pvc-pod.yaml\nまた, StorageClassの設定によっては,\n事前にPersistentVolumeを用意しなくてもPersistentVolumeClaimに応じたPersistentVolumeを自動で払い出すことができる(Dynamic Provisioning).\n# GKEのデフォルトStorageClass $ kubectl describe sc standard Name: standard IsDefaultClass: Yes Annotations: storageclass.kubernetes.io/is-default-class=true Provisioner: kubernetes.io/gce-pd Parameters: type=pd-standard AllowVolumeExpansion: True MountOptions: \u0026lt;none\u0026gt; ReclaimPolicy: Delete VolumeBindingMode: Immediate Events: \u0026lt;none\u0026gt; # PersistentVolumeがない状態でStorageClassを指定しないPersistentVolumeClaimを作成する $ kubectl get pv No resources found. $ kubectl apply -f sample-pvc-default-storageclass.yaml persistentvolumeclaim/sample-pvc created # GCPのディスクとPersistentVolumeが自動で払い出されている $ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pvc-332c4de9-ce56-11ea-a3f9-42010a80005d 4Gi RWO Delete Bound default/sample-pvc standard 69s $ gcloud compute disks list NAME LOCATION LOCATION_SCOPE SIZE_GB TYPE STATUS gke-k8s-01-08fea67e-dy-pvc-332c4de9-ce56-11ea-a3f9-42010a80005d us-central1-a zone 4 pd-standard READY # 使用したPersistentVolumeClaimはsample-pvc.yamlを改変したもの $ cat sample-pvc-default-storageclass.yaml kind: PersistentVolumeClaim apiVersion: v1 metadata: name: sample-pvc spec: resources: requests: storage: 4Gi accessModes: - ReadWriteOnce おまけ ","date":"2020-07-25T17:57:31+09:00","image":"/post/2020-07-25-kubernetes-guide-chap7/sotochan.jpg","permalink":"/post/2020-07-25-kubernetes-guide-chap7/","title":"Kubernetes完全に理解したい 7章"},{"content":"Web APIつくってみる Web APIを自分で最初から作ったことがなかったので, Goの練習も兼ねてやってみた.\nやったことのまとめ 以下のようにランダムなサイコロの出目(number)とサイコロの面の数(faces)をJSONで返すだけのWeb APIをGoで作った.\n# 普通に叩くと6面サイコロを振る $ curl -X GET \u0026#34;localhost:8080\u0026#34; {\u0026#34;number\u0026#34;:1,\u0026#34;faces\u0026#34;:6} $ curl -X GET \u0026#34;localhost:8080\u0026#34; {\u0026#34;number\u0026#34;:5,\u0026#34;faces\u0026#34;:6} # クエリパラメータで面の数を指定できる $ curl -X GET \u0026#34;localhost:8080?faces=100\u0026#34; {\u0026#34;number\u0026#34;:71,\u0026#34;faces\u0026#34;:100} # ズルをするためのエンドポイントも用意 $ curl -X GET \u0026#34;localhost:8080/cheat\u0026#34; {\u0026#34;number\u0026#34;:6,\u0026#34;faces\u0026#34;:6} $ curl -X GET \u0026#34;localhost:8080/cheat?faces=100\u0026amp;number=99\u0026#34; {\u0026#34;number\u0026#34;:99,\u0026#34;faces\u0026#34;:100} つかうもの macOS Mojave 10.14 anyenv 1.1.1 インストール済み goenv 2.0.0beta11 インストール済み go version go1.14.6 darwin/amd64 今回入れる やったこと 準備 サイコロの作成 HTTPハンドラの作成 テストの作成 準備 まずは開発環境の準備.\n公式によると現在(2020/07/16)Goの最新版が 1.14.6 なので, これを使うようにする.\n# goenvの更新 $ anyenv install goenv anyenv: /Users/uzimihsr/.anyenv/envs/goenv already exists Reinstallation keeps versions directories continue with installation? (y/N) y ... Install goenv succeeded! Please reload your profile (exec $SHELL -l) or open a new session. $ exec $SHELL -l # Go 1.14.6のインストール $ cd $ goenv install 1.14.6 $ goenv global 1.14.6 $ goenv rehash $ exec $SHELL -l # 確認 $ goenv version 1.14.6 (set by /Users/uzimihsr/.anyenv/envs/goenv/version) $ go version go version go1.14.6 darwin/amd64 $ echo $GOPATH /Users/uzimihsr/go/1.14.6 次にGitHubリポジトリを新規作成する.\n作ったリポジトリ : https://github.com/uzimihsr/dice-api\n作成したリポジトリをローカルにcloneして, Go Modulesとか.gitignoreの準備をする.\nGo Modulesの使い方は公式を参考にする.\n.gitignoreはgitignore.ioを使って作るのが楽.\n# 適当なディレクトリで作業 $ cd workspace $ git clone https://github.com/uzimihsr/dice-api.git Cloning into \u0026#39;dice-api\u0026#39;... warning: You appear to have cloned an empty repository. $ cd dice-api # Go Moduleの初期化 $ go mod init github.com/uzimihsr/dice-api go: creating new go.mod: module github.com/uzimihsr/dice-api $ ls go.mod # gitignore.ioのAPIを利用して.gitignoreを作成する $ curl -o ./.gitignore https://www.toptal.com/developers/gitignore/api/go,macos,linux # いったんpushしておく $ echo \u0026#34;# dice-api\u0026#34; \u0026gt; README.md $ git add . $ git commit -m \u0026#34;first commit\u0026#34; $ git push origin master このときのリポジトリはこんなかんじ.\nサイコロの作成 まずはサイコロの動作を作ってみる.\n以前作ったものを流用する.\n$ mkdir dice \u0026amp;\u0026amp; cd dice $ vim dice.go 今回は普通にサイコロの出目を返すメソッドRoll()の他に指定した出目を返すCheat()を作ってみた.\nためしに動かしてみる.\n$ cd .. $ vim main.go $ go run main.go 6 5 $ go run main.go 1 5 いい感じ.\nHTTPハンドラの作成 次にこれをWeb APIとして動かすためのハンドラ関数を作る.\n$ mkdir handler \u0026amp;\u0026amp; cd handler $ vim handler.go ポイントはハンドラ関数DiceHandler(), CheatDiceHandler()の引数でinterfaceを受けるようにしているところ.\nこうしておくと後でテストを書くときにmockを使いやすくなる.\nこれらのハンドラ関数を扱ってHTTPサーバーを動かすため, main.goを修正する.\n$ cd .. $ vim main.go $ go run main.go # 動作を確認したら Ctrl+C で終了\nこちらはhttp.NewServeMux()を使ってリクエストパスごとに異なるハンドラを呼び出すようにしているのがポイント.\nmain.goを実行するとMacの警告が出るので, 許可を選択するとアプリが動く.\n試しに別のターミナルからAPIを叩いてみる.\n# 正常系 $ curl \u0026#34;localhost:8080\u0026#34; {\u0026#34;number\u0026#34;:1,\u0026#34;faces\u0026#34;:6} $ curl \u0026#34;localhost:8080?faces=12\u0026#34; {\u0026#34;number\u0026#34;:10,\u0026#34;faces\u0026#34;:12} $ curl \u0026#34;localhost:8080/cheat\u0026#34; {\u0026#34;number\u0026#34;:6,\u0026#34;faces\u0026#34;:6} $ curl \u0026#34;localhost:8080/cheat?number=1\u0026#34; {\u0026#34;number\u0026#34;:1,\u0026#34;faces\u0026#34;:6} $ curl \u0026#34;localhost:8080/cheat?number=1\u0026amp;faces=18\u0026#34; {\u0026#34;number\u0026#34;:1,\u0026#34;faces\u0026#34;:18} # 異常系 $ curl -i -X POST \u0026#34;localhost:8080\u0026#34; HTTP/1.1 404 Not Found Content-Type: application/json Date: Thu, 23 Jul 2020 08:12:39 GMT Content-Length: 24 {\u0026#34;message\u0026#34;: \u0026#34;not found\u0026#34;} $ curl -i -X GET \u0026#34;localhost:8080?faces=hoge\u0026#34; HTTP/1.1 500 Internal Server Error Content-Type: text/plain; charset=utf-8 X-Content-Type-Options: nosniff Date: Thu, 23 Jul 2020 08:13:04 GMT Content-Length: 45 strconv.Atoi: parsing \u0026#34;hoge\u0026#34;: invalid syntax $ curl -i -X POST \u0026#34;localhost:8080/cheat\u0026#34; HTTP/1.1 404 Not Found Content-Type: application/json Date: Thu, 23 Jul 2020 08:13:58 GMT Content-Length: 24 {\u0026#34;message\u0026#34;: \u0026#34;not found\u0026#34;} $ curl -i -X GET \u0026#34;localhost:8080/cheat?number=hoge\u0026#34; HTTP/1.1 500 Internal Server Error Content-Type: text/plain; charset=utf-8 X-Content-Type-Options: nosniff Date: Thu, 23 Jul 2020 08:14:12 GMT Content-Length: 45 strconv.Atoi: parsing \u0026#34;hoge\u0026#34;: invalid syntax サイコロの出目(number)と面の数(faces)がJSONで返ってきて,\n異常なクエリパラメータやHTTPメソッドでリクエストした場合には指定したステータスコード(404, 500)が返ってくることが確認できた.\nやったぜ.\nこのときのリポジトリはこんなかんじ.\nテストの作成 作りたいものは作れたのでここで終わってもいいけど, せっかくなのでテストコードを書いてみる.\nまずはdiceパッケージから.\n$ cd ./dice $ vim dice_test.go カバレッジ100%になるように書いたけど, 無駄なテストも含まれている.\n次にhandlerパッケージのテストを作る.\nhandlerパッケージは自作のdiceパッケージに依存しているので, diceパッケージのmockを用意したい.\n(今回はDBとかに接続してないのでdiceを直接呼び出してもいいんだけど, サイコロの出目が確率で変わっちゃうのでテストがしづらい)\nこんなときにはgomockを使う.\nmockしたいinterfaceのファイルを指定するだけでmock用のコードを作成してくれるのでめちゃ便利.\n# mockパッケージのインストール $ cd .. $ go get github.com/golang/mock/mockgen # diceのmockを作成 $ mockgen -source ./dice/dice.go -destination ./mock_dice/mock_dice.go このmockを使ったテストを書いてみる.\n$ cd handler $ vim handler_test.go 準備したmockを各ハンドラ関数の引数に渡してやることで, 実際のdiceを呼び出すことなくハンドラ関数のテストができるようになる.\nハンドラ関数を作るときにdiceのinterfaceを引数で受けるようにしたのはこのため.\nテストコードが作れたので, 実際にテストを実行する.\n$ cd .. # diceパッケージのテスト $ go test -cover ./dice ok github.com/uzimihsr/dice-api/dice\t0.006s\tcoverage: 100.0% of statements # handlerパッケージのテスト $ go test -cover ./handler ok github.com/uzimihsr/dice-api/handler\t0.016s\tcoverage: 100.0% of statements # 全部まとめてテスト $ go test -cover ./... ? github.com/uzimihsr/dice-api\t[no test files] ok github.com/uzimihsr/dice-api/dice\t(cached)\tcoverage: 100.0% of statements ok github.com/uzimihsr/dice-api/handler\t(cached)\tcoverage: 100.0% of statements ? github.com/uzimihsr/dice-api/mock_dice\t[no test files] これでテストも(一応)できた.\n最終的なディレクトリの構成はこんなかんじ.\nテストを実行したのでパッケージの依存関係を管理するためのファイルgo.modとgo.sumが修正/追加されている.\nこいつらも一緒にGitで管理しておくと, 別の環境でこれをビルドするときに便利だったりする. らしい.\n$ tree . . ├── README.md ├── dice │ ├── dice.go │ └── dice_test.go ├── go.mod ├── go.sum ├── handler │ ├── handler.go │ └── handler_test.go ├── main.go └── mock_dice └── mock_dice.go 3 directories, 9 files 最終的なリポジトリはこんなかんじ.\nおわり Web APIっぽいものを0から作ってみた. テストまでやったのでけっこうしんどかった.\nあとはビルドすれば普通に動くはずなので,\n暇があったらKubernetesで動かすところまでやってみたい.\nおまけ 参考にしたもの https://github.com/golang/go/wiki/Modules#example Goプログラミング実践入門 標準ライブラリでゼロからWebアプリを作る https://github.com/golang/mock/blob/master/README.md ","date":"2020-07-23T14:10:10+09:00","image":"/post/2020-07-23-golang-dice-api/sotochan.jpg","permalink":"/post/2020-07-23-golang-dice-api/","title":"GoでサイコロAPIを作る"},{"content":"いらないメトリクスは拾いたくない Prometheusでスクレイプする対象にいらないメトリクスがあったときに無視したかった.\nまとめ 特定のメトリクスだけ除外したいときはmetric_relabel_configsで除外したいメトリクスのラベルを正規表現で記述してdropするのが良さそう.\n# メトリクス名がnode_cpu_seconds_totalでmode=\u0026#34;idle\u0026#34;のラベルを持つメトリクスを除外する例 scrape_configs: - job_name: \u0026#39;node\u0026#39; static_configs: - targets: [\u0026#39;localhost:9100\u0026#39;] metric_relabel_configs: - source_labels: [__name__, mode] regex: \u0026#39;node_cpu_seconds_total;idle\u0026#39; action: drop 環境 Raspberry Pi 3 Model B+ OSはRaspbian(10.0) 監視サーバとして使用 Prometheus version 2.15.2 インストール済み Node exporter version 0.18.1 インストール済み やりかた 例えばNode exporterで取れるCPU時間のメトリクスnode_cpu_seconds_totalをPrometheusで見ているとき,\n普通はこんな感じでmodeラベルの値が異なる複数のメトリクスが取れる.\nnode_cpu_seconds_total{cpu=\u0026#34;0\u0026#34;} メトリクスの用途はさておき, この中でmode=\u0026quot;idle\u0026quot;のラベルがついているメトリクスだけを除外したいとする.\nそんなときはPrometheusのスクレイプ設定でmetric_relabel_configsの設定を追加する.\nこの設定により, __name__ラベル(メトリクス名)がnode_cpu_seconds_totalかつmodeラベルがidleであるメトリクスだけが除外(drop)される.\n設定変更後, Prometheusを再起動する.\n# prometheusのスクレイプ設定を変更して再起動 $ vim /usr/local/prometheus/prometheus.yml $ sudo systemctl restart prometheus 再度Prometheusで先程と同じクエリを投げると, mode=\u0026quot;idle\u0026quot;のラベルがついたメトリクスだけがなくなっていることがわかる.\n今度は逆にmode=\u0026quot;idle\u0026quot;のラベルがついているメトリクスだけを残して, 他のメトリクスを拾わないようにしてみる.\nこの設定により, __name__=\u0026quot;node_cpu_seconds_total\u0026quot;とmode=\u0026quot;idle\u0026quot;の両方のラベルを持つメトリクスのみが保持(keep)され,\nそれ以外のメトリクスがすべて除外される.\nこれでも問題なさそうだけど, 実はこの設定を入れるとメトリクス名(__name__)がnode_cpu_seconds_totalでないメトリクスがすべて捨てられてしまうので,\nnode_filesystem_free_bytes(ファイルシステムの空き容量)のようなNode exporterの他のメトリクスが取れなくなってしまう.\nうまいこと__name__=\u0026quot;node_cpu_seconds_total\u0026quot;かつmode!=\u0026quot;idle\u0026quot;のような条件を正規表現で書ければいいんだけど,\nPrometheusで使うRE2正規表現は(?!idle)みたいな否定先読みに対応していないらしいのでちょっと難しい.\nよくわかんなかったので苦肉の策として除外したい個別のラベルを列挙してみた.\nこれなら__name__=\u0026quot;node_filesystem_free_bytes\u0026quot;のような他のメトリクスを捨てずに__name__=\u0026quot;node_cpu_seconds_total\u0026quot;のメトリクスの中でmode=\u0026quot;idle\u0026quot;なものだけを残せるけど,\n除外するラベルを全部列挙するのはあんまり現実的じゃないような気がする\u0026hellip;\nおわり 正規表現にマッチするメトリクスだけ除外するdropとその逆のkeepだと本当は除外対象を列挙しないで済むkeepを使いたくなるけど,\n現状は正規表現の仕様でdropのほうが使いやすいという感じがする.\nそもそもそこまで排除対象のメトリクスが多いようなスクレイプ対象だったらそれ自体を見直したほうがいいのだろうか.\nとりあえずはdropで頑張ってみて, keepでいい感じに他のメトリクスを残せるような方法があったら試してみたい.\nおまけ 参考 https://www.robustperception.io/dropping-metrics-at-scrape-time-with-prometheus https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config https://github.com/google/re2/wiki/Syntax ","date":"2020-07-14T22:49:32+09:00","image":"/post/2020-07-14-prometheus-metric-relabel/sotochan.jpg","permalink":"/post/2020-07-14-prometheus-metric-relabel/","title":"Prometheusで不要なメトリクスを除外する"},{"content":"[注意]この記事の執筆時点ではyq version3系を使用していたため、コマンド例にversion4系で使用できないオプションが含まれています。\n→yq(v4)でKubernetesのYAMLをいじる\nyqすごい yqでKubernetesのYAMLから値を抽出したり値を書き換えたりした.\nまとめ yqはべんり.\n# YAMLの値(key=spec.containers[0].image)を抽出 $ yq read \u0026#39;pod.yaml\u0026#39; \u0026#39;spec.containers[0].image\u0026#39; # 正規表現でimageとtagを抜き出す $ yq read \u0026#39;pod.yaml\u0026#39; \u0026#39;spec.containers[0].image\u0026#39; | grep -o \u0026#39;^[^:]\\+\u0026#39; $ yq read \u0026#39;pod.yaml\u0026#39; \u0026#39;spec.containers[0].image\u0026#39; | grep -o \u0026#39;[^:]\\+$\u0026#39; # YAMLの値(key=spec.containers[0].image)を上書き $ yq write -i \u0026#39;pod.yaml\u0026#39; \u0026#39;spec.containers[0].image\u0026#39; \u0026#39;nginx:1.19.0\u0026#39;\n環境 macOS Mojave 10.14 yq version 3.3.2 やりかた こんな感じのKubernetesのマニフェストファイル(YAML)を扱っていたときに,\n使用しているimage(nginx)のtagが latest だったのを 1.19.0 みたいなimageのdigestが一意に定まるようなバージョンごとのtagに変更したくなった.\npod.yaml\n手で直接編集するのもいいんだけど,\nゆくゆくは自動化したりしたいのでyqを使ってスクリプトを書いてみる.\nimage-tag-fix.sh\n作成したスクリプトを実行する.\n$ ls image-tag-fix.sh pod.yaml # タグ置換スクリプト実行 $ chmod +x ./image-tag-fix.sh $ ./image-tag-fix.sh image : nginx tag : latest replaced: nginx:latest =\u0026gt; nginx:1.19.0 # 内容確認 $ cat pod.yaml apiVersion: v1 kind: Pod metadata: name: nginx-pod spec: containers: - name: nginx image: nginx:1.19.0 いい感じ.\nyqを使ってYAMLの値を書き換えることができた.\nおわり yqはjqみたいな感じでYAMLを扱えるのでかなり便利.\nうまく使いこなせるようになりたい.\nおまけ 参考 https://github.com/mikefarah/yq/blob/master/README.md ","date":"2020-07-06T17:58:37+09:00","image":"/post/2020-07-06-yq-kubernetes-yaml/sotochan.jpg","permalink":"/post/2020-07-06-yq-kubernetes-yaml/","title":"yq(v3)でKubernetesのYAMLをいじる"},{"content":"じめじめするけど 6月もそとちゃんは元気だった.\nまとめ おもちゃがたくさん ねこがおちている はみがきできない おもちゃがたくさん 先月はあまり遊んであげられなかったので,\n今月はおもちゃでたくさん遊んであげた.\nペットショップ行けたのでバチクソおもちゃ買ってあげた(失くしたねずみのおもちゃ含む) pic.twitter.com/k4CZJNWD8r\n\u0026mdash; ずみし (@uzimihsr) June 2, 2020 特に小さいねずみのおもちゃが最近のお気に入りらしい.\n箱の中に入れるとずっといじって遊んでる.\nねずみの入った箱に夢中 pic.twitter.com/Za5H5Iy7Ym\n\u0026mdash; ずみし (@uzimihsr) June 4, 2020 頭突っ込んでるのかわいい pic.twitter.com/MtEJgOL3Go\n\u0026mdash; ずみし (@uzimihsr) June 4, 2020 この大玉のおもちゃはそんなに食いつかなかった\u0026hellip;\n自分で持ち上げられるくらいの軽さのおもちゃが好きみたい.\nあたらしいおもちゃ pic.twitter.com/EqbMGAtRMg\n\u0026mdash; ずみし (@uzimihsr) June 26, 2020 運動量を増やそうとしてねこじゃらしのレベルを上げるとすぐ諦めちゃうのがかわいかった.\n今月買ったおもちゃたちの現在.\nじゃらしは3/5が破壊, ねずみは4つ出して2つが行方不明\u0026hellip;\n遊び方が激しすぎませんかね\u0026hellip;\nねこがおちている 梅雨でじめじめしているのと関係があるかはわからないけど,\n家にねこがおちていることが多くなった.\n人間のベッドにねこが落ちてた pic.twitter.com/EtDdIRFwrY\n\u0026mdash; ずみし (@uzimihsr) June 7, 2020 バテた pic.twitter.com/JbcVqTDykU\n\u0026mdash; ずみし (@uzimihsr) June 8, 2020 元気がないわけじゃないのでそこまで気にしてないけど,\nあんまりひなたぼっこできないから退屈なのかもしれない.\n朝 pic.twitter.com/DwSURYPUcd\n\u0026mdash; ずみし (@uzimihsr) June 18, 2020 のびる pic.twitter.com/XQTgzSD7T4\n\u0026mdash; ずみし (@uzimihsr) June 25, 2020 あとは単純に遊びすぎて疲れてる説もある.\n溶けてるみたいでかわいい.\nはみがきできない 将来のためにはみがきの練習を始めたんだけど, めちゃくちゃ嫌いみたい\u0026hellip;\nはみがきシートの匂いを嗅ぐだけで嫌な顔する.\n大好物のにぼしで釣ってなんとか歯には触らせてくれるようにはなったけど,\n終わった後はいつもいじける.\nおやつで釣られて歯みがきさせられたからもう何も信じたくなくなったねこ(日課) pic.twitter.com/eaWzS1wXge\n\u0026mdash; ずみし (@uzimihsr) June 17, 2020 ↓の動画を参考にちょっとずつやってるけど,\n成猫になってからなのでなかなか慣れてくれない.\nこればかりは気長に頑張るしかないのかもしれない\u0026hellip;\nしもべはこんなにそとちゃんの健康を考えているのに,\nそとちゃんはお手入れのたびにしもべのことが嫌いになっていく. かなしい😭\nおわり そとちゃんは6月も元気だった.\nひなたぼっこできないのはかわいそうなので, はやく梅雨明けしてほしい.\nはみがきできるようになる日は来るのか\u0026hellip;?\nおまけ ","date":"2020-07-01T22:33:16+09:00","image":"/post/2020-07-01-sotochan/sotochan03.jpg","permalink":"/post/2020-07-01-sotochan/","title":"6月のそとちゃんまとめ(2020)"},{"content":"awkすき Shellで文字列を扱うときにちょっとだけ困ったのでメモ.\nまとめ substr()とlength()を使う.\n# 先頭から1文字削除 $ echo \u0026#39;abcdefghijkl\u0026#39; | awk \u0026#39;{print substr($0, 2)}\u0026#39; bcdefghijkl # 末尾から1文字削除 $ echo \u0026#39;abcdefghijkl\u0026#39; | awk \u0026#39;{print substr($0, 1, length($0)-1)}\u0026#39; abcdefghijk # 先頭と末尾から1文字ずつ削除 $ echo \u0026#39;abcdefghijkl\u0026#39; | awk \u0026#39;{print substr($0, 2, length($0)-2)}\u0026#39; bcdefghijk\n環境 macOS Mojave 10.14 awk version 20070501 やりかた substr(string, start, length)は文字列(string)の頭n文字目(start)からm文字(length)を抜き出して表示する.\n引数のlengthは省略可能で, 省略した場合は文末まで表示される.\n# qwerty の2文字目(w)から末尾まで抽出 $ awk \u0026#39;BEGIN {print substr(\u0026#34;qwerty\u0026#34;, 2)}\u0026#39; werty # qwerty の2文字目(w)から4文字ぶんを抽出 $ awk \u0026#39;BEGIN {print substr(\u0026#34;qwerty\u0026#34;, 2, 4)}\u0026#39; wert 先頭から文字を抜き出せたが, 末尾から文字を抜き出したい場合もある.\nlength(string)は文字列(string)の文字数を数えてくれるので, これとsubstr()を組み合わせてみる.\n# qwertyの文字数は6 $ awk \u0026#39;BEGIN {print length(\u0026#34;qwerty\u0026#34;)}\u0026#39; 6 # qwerty の1文字目(q)から(length-1=5)文字ぶんを抽出 $ awk \u0026#39;BEGIN {print substr(\u0026#34;qwerty\u0026#34;, 1, length(\u0026#34;qwerty\u0026#34;)-1)}\u0026#39; qwert あとはパイプでつないでいい感じにすればいい.\n# ダブルクォート(\u0026#34;\u0026#34;)で囲まれた文字列(\u0026#34;qwerty\u0026#34;)から文字列(qwerty)のみを抽出 $ echo \u0026#39;\u0026#34;qwerty\u0026#34;\u0026#39; | awk \u0026#39;{print substr($0, 2, length($0)-2)}\u0026#39; qwerty やったぜ.\nおわり やっぱりawkは神.\nもっと使いこなしていきたい.\nおまけ 参考 https://www.gnu.org/software/gawk/manual/html_node/String-Functions.html ","date":"2020-06-29T21:19:05+09:00","image":"/post/2020-06-29-print-string-awk/sotochan.jpg","permalink":"/post/2020-06-29-print-string-awk/","title":"awkコマンドを使って文字列の先頭と末尾を削除する"},{"content":"特定の人だけ許可したい サーバー証明書のしくみを学んだので, 今度はクライアント認証のしくみを作ってみる.\nやったことのまとめ 認証局を立ててクライアント証明書を作成した nginxでクライアント認証の設定をした MacからcurlとChromeでクライアント認証のかかったnginxに接続した サーバー証明書がサーバーの正当性を証明してクライアントがサーバーを信頼するためのものであるのに対し,\nクライアント証明書はその逆でクライアントの正当性を証明してサーバーがクライアントを信頼するためのもの.\n不特定多数の相手に公開したくないサーバーにクライアント認証をかけることで,\n有効なクライアント証明書を持つ特定の相手だけにこれを公開することができる.\nつかうもの Raspberry Pi 3 Model B+ OSはRaspbian(10.0) SSLサーバーとして使用 IP固定済み OpenSSL OpenSSL 1.1.1c 28 May 2019 ラズパイの初期装備 nginx version 1.14.2 ラズパイにインストール済み macOS Mojave 10.14 クライアントとして使用 Google Chrome バージョン: 83.0.4103.61（Official Build） （64 ビット） やったこと クライアント証明書の作成 nginxの設定 クライアント証明書を用いた接続 クライアント証明書の作成 まずはクライアント証明書を作成する.\nといっても途中までの手順はサーバー証明書を作成したときとほとんど同じ.\nまずはオレオレ認証局を建てる.\n## 以下すべてラズパイで実行 # オレオレ認証局用ディレクトリ(ClientCA)で作業 $ mkdir ClientCA \u0026amp;\u0026amp; cd ClientCA # 認証局用の秘密鍵(ClientCA-private-key.pem)を作成 $ openssl genpkey -algorithm RSA -out ClientCA-private-key.pem # 認証局の秘密鍵(ClientCA-private-key.pem)を使ったオレオレ証明書(ClientCA.pem)の作成 $ openssl req -x509 -key ClientCA-private-key.pem -out ClientCA.pem You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter \u0026#39;.\u0026#39;, the field will be left blank. ----- Country Name (2 letter code) [AU]: State or Province Name (full name) [Some-State]: Locality Name (eg, city) []: Organization Name (eg, company) [Internet Widgits Pty Ltd]: Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []:ClientCA Email Address []: # 署名のための準備 $ mkdir -p demoCA/newcerts $ touch ./demoCA/index.txt $ echo 01 \u0026gt; ./demoCA/serial 次にクライアント証明書用の秘密鍵と証明書署名要求(CSR)を作成する.\n# クライアント用ディレクトリ(Client)で作業 $ cd ../ $ mkdir Client \u0026amp;\u0026amp; cd Client # クライアント用の秘密鍵(Client-private-key.pem)を作成 $ openssl genpkey -algorithm RSA -out Client-private-key.pem # クライアントの秘密鍵(Client-private-key.pem)からCSR(Client-csr.pem)を作成 $ openssl req -new -key Client-private-key.pem -out Client-csr.pem You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter \u0026#39;.\u0026#39;, the field will be left blank. ----- Country Name (2 letter code) [AU]: State or Province Name (full name) [Some-State]: Locality Name (eg, city) []: Organization Name (eg, company) [Internet Widgits Pty Ltd]: Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []:Client Email Address []: Please enter the following \u0026#39;extra\u0026#39; attributes to be sent with your certificate request A challenge password []: An optional company name []: 次はクライアントのCSRを認証局で署名してクライアント証明書を作成する.\nこれによりこの証明書を持つクライアントの正当性を認証局(ClientCA)が証明したことになる.\n# クライアントのCSR(Client-csr.pem)を認証局(ClientCA)に渡す $ cp Client-csr.pem ../ClientCA/ # 認証局の秘密鍵(ClientCA-private-key.pem)と証明書(ClientCA.pem)で # クライアントのCSR(Client-csr.pem)に署名してクライアント証明書(Client.pem)を作成 $ cd ../ClientCA $ openssl ca -in Client-csr.pem -out Client.pem -keyfile ClientCA-private-key.pem -cert ClientCA.pem Using configuration from /usr/lib/ssl/openssl.cnf Check that the request matches the signature Signature ok Certificate Details: Serial Number: 1 (0x1) Validity Not Before: Jun 24 14:02:29 2020 GMT Not After : Jun 24 14:02:29 2021 GMT Subject: countryName = AU stateOrProvinceName = Some-State organizationName = Internet Widgits Pty Ltd commonName = Client X509v3 extensions: X509v3 Basic Constraints: CA:FALSE Netscape Comment: OpenSSL Generated Certificate X509v3 Subject Key Identifier: 13:E6:B2:5D:88:06:CC:2C:17:BE:AA:98:92:B2:09:C3:BF:D4:AA:A4 X509v3 Authority Key Identifier: keyid:9C:C0:54:1F:93:4C:F1:F9:0A:6D:2B:AF:8E:B0:80:54:F1:2A:EC:F3 Certificate is to be certified until Jun 24 14:02:29 2021 GMT (365 days) Sign the certificate? [y/n]:y 1 out of 1 certificate requests certified, commit? [y/n]y Write out database with 1 new entries Data Base Updated # クライアントに証明書(Client.pem)を渡す $ mv ./Client.pem ../Client/ さいごにクライアントの秘密鍵と証明書をくっつけてPKCS#12形式に変換する.\nなんかしらんけどクライアント認証で使うときに一番メジャーなフォーマットらしい.\n# 証明書(Client.pem)と秘密鍵(Client-private-key.pem)からPKCS#12形式のファイル(Client.p12)を作成 $ cd ../Client/ $ openssl pkcs12 -export -clcerts -in Client.pem -inkey Client-private-key.pem -out Client.p12 Enter Export Password: # 任意のパスワードを設定する Verifying - Enter Export Password: # 再度パスワードを入力 以上の手順でクライアント証明書と秘密鍵をあわせたPKCS#12形式ファイルが作成できた.\nこれでクライアント側の準備は完了.\nnginxの設定 次にnginxでクライアント認証の設定を行う.\nまずはクライアント証明書を発行した認証局の証明書をnginx用のディレクトリに配置する.\n# CA証明書(ClientCA.pem)をnginx用ディレクトリに配置 $ cd .. $ sudo cp ClientCA/ClientCA.pem /etc/nginx/ $ ls /etc/nginx/ClientCA.pem /etc/nginx/ClientCA.pem 次にnginxの設定を変更して再起動する.\n# nginxの設定ファイル(https.conf)を編集 $ sudo vim /etc/nginx/conf.d/https.conf # nginx再起動 $ sudo nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful $ sudo systemctl restart nginx 今回変更した部分以外(サーバー証明書など)は前回と全く同じ状態.\nこれでnginxにはクライアント認証がかかり,\n信頼しているCA証明書(ClientCA.pem)の認証局によって署名されたクライアント証明書を持つ相手のみを信頼して内容を公開するようになった.\nクライアント証明書を用いた接続 サーバー(nginx)側の設定が終わったので, 試しにMacからnginxに接続してみる.\nなにもしない状態でcurlで https://\u0026lt;ラズパイのIP\u0026gt;/ をたたくとクライアント証明書がないので怒られる.\n## 以下はすべてMacから実行 # nginx(ラズパイ)にcurlしてみる # nginxの持ってるサーバーの証明書が信頼できないものなので --insecure をつけて実行する $ curl https://raspberrypi --insecure \u0026lt;html\u0026gt; \u0026lt;head\u0026gt;\u0026lt;title\u0026gt;400 No required SSL certificate was sent\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body bgcolor=\u0026#34;white\u0026#34;\u0026gt; \u0026lt;center\u0026gt;\u0026lt;h1\u0026gt;400 Bad Request\u0026lt;/h1\u0026gt;\u0026lt;/center\u0026gt; \u0026lt;center\u0026gt;No required SSL certificate was sent\u0026lt;/center\u0026gt; \u0026lt;hr\u0026gt;\u0026lt;center\u0026gt;nginx/1.14.2\u0026lt;/center\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; # クライアント証明書がないので怒られる 次にMacにPKCS#12形式のクライアント証明書をもたせた状態で接続してみる.\n今度はnginx側が持つCA証明書とMacが持つクライアント証明書の照合が行われ,\n問題がなければクライアント認証を突破できる.\n# ラズパイからMacにPKCS#12のクライアント証明書を持ってくる $ scp pi@raspberrypi://path/to/Client/Client.p12 ./ # PKCS#12形式のファイル(Client.p12)からクライアント証明書(Client.pem)を取り出す $ openssl pkcs12 -in ./Client.p12 -out Client.pem -clcerts -nokeys Enter Import Password: # pkcs12作成時に設定したパスワードを入力 MAC verified OK # PKCS#12形式のファイル(Client.p12)からクライアント秘密鍵(Client-private-key.pem)を取り出す # 秘密鍵にパスフレーズをつけていないので -nodes をつける $ openssl pkcs12 -in ./Client.p12 -out Client-private-key.pem -nocerts -nodes Enter Import Password: # pkcs12作成時に設定したパスワードを入力 MAC verified OK # クライアントの証明書(Client.pem)と秘密鍵(Client-private-key.pem)をもたせた状態でcurlしてみると認証に成功してHTMLが表示される $ curl --key ./Client-private-key.pem --cert ./Client.pem https://raspberrypi --insecure \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;Welcome to nginx!\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1\u0026gt;Welcome to nginx!\u0026lt;/h1\u0026gt; \u0026lt;p\u0026gt;If you see this page, the nginx web server is successfully installed and working. Further configuration is required.\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;For online documentation and support please refer to \u0026lt;a href=\u0026#34;http://nginx.org/\u0026#34;\u0026gt;nginx.org\u0026lt;/a\u0026gt;.\u0026lt;br/\u0026gt; Commercial support is available at \u0026lt;a href=\u0026#34;http://nginx.com/\u0026#34;\u0026gt;nginx.com\u0026lt;/a\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;\u0026lt;em\u0026gt;Thank you for using nginx.\u0026lt;/em\u0026gt;\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; ついでにブラウザでも試してみる.\n何もしない状態で https://\u0026lt;ラズパイのIP\u0026gt;/ をChromeで開くと証明書がないから怒られる.\nMacにクライアント証明書をもたせてみる.\n先程ラズパイから持ってきたPKCS#12のファイルをダブルクリックする.\nパスワードを求められるのでPKCS#12の作成時に設定したパスワードを入力する.\nキーチェーンアクセスが開くので, この画面でPKCS#12ファイルをダブルクリック.\n詳細画面が開くので, 信頼-\u0026gt;この証明書を信頼するときを常に信頼に変更してウィンドウを閉じる.\nこれでMacがこのクライアント証明書を使えるようになった.\n再度 https://\u0026lt;ラズパイのIP\u0026gt;/ をChromeで開く.\n今度はこのサーバー(nginx)のクライアント認証に使用するクライアント証明書を選択する画面が開くので, OK を選択する.\nこれでChromeがクライアント証明書を持ってnginxにアクセスするようになる.\nクライアント認証に成功し,\nnginxのスタートページのHTMLが表示された.\nやったぜ.\nこれでクライアント認証のしくみを実装することができた.\nおわり 以上の手順でOpenSSLとnginxを使ったクライアント認証のしくみを試してみた.\n不特定多数に公開したくない独自APIをチーム内だけに公開するときとかにサクッと作れると便利そう.\nおまけ 参考 クライアント証明書の作成 nginxとOpenSSLでHTTPSサーバーを立てる https://www.openssl.org/docs/man1.1.0/man1/pkcs12.html nginxの設定 https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_client_certificate https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_verify_client クライアント証明書を用いた接続 https://curl.haxx.se/docs/manpage.html https://support.apple.com/ja-jp/guide/keychain-access/kyca2431/10.5/mac/10.14 ","date":"2020-06-26T23:32:10+09:00","image":"/post/2020-06-26-client-certification-practice/sotochan.jpg","permalink":"/post/2020-06-26-client-certification-practice/","title":"nginxとOpenSSLでクライアント認証を行う"},{"content":"オレオレ証明書でHTTPS OpenSSLで証明書を作る方法を勉強したので, nginxと組み合わせてHTTPSサーバーを立ててみる.\nやったことのまとめ オレオレ証明書を使ってルート認証局を立てた ルート認証局を使って中間認証局を立てた 中間認証局でサーバー証明書に署名した サーバー証明書をnginxにインストールしてブラウザで確認した サーバー証明書とはHTTPSで通信する際にサーバーの正当性を認証局が証明していることを示すもの.\nクライアントがHTTPSサーバーと通信する際はサーバーの証明書を確認してサーバーの正当性を誰(認証局)が証明しているのかを確認する.\nさらにその認証局(中間認証局)の正当性を証明するのがルート認証局で,\nクライアントは最初から信頼できるルート認証局の情報を持っているので証明書の発行者をたどることでそのサーバーの正当性を確認することができる.\n今回はこのしくみをローカルでつくって試してみる.\nOpenSSLの使い方とかは既にメモを作成済み.\nOpenSSLで秘密鍵と公開鍵を作る OpenSSLで共通鍵暗号方式とハイブリッド暗号方式を試す OpenSSLでデジタル証明書を試す つかうもの Raspberry Pi 3 Model B+ OSはRaspbian(10.0) SSLサーバーとして使用 OpenSSL OpenSSL 1.1.1c 28 May 2019 ラズパイの初期装備 nginx version 1.14.2 インストール済み macOS Mojave 10.14 ブラウザ用 Google Chrome バージョン: 83.0.4103.61（Official Build） （64 ビット） やったこと オレオレ証明書とルート認証局の作成 中間証明書と中間認証局の作成 サーバー証明書の作成 nginxに証明書を持たせる オレオレ証明書とルート認証局の作成 本来であれば正式な認証局に依頼してちゃんとしたサーバー証明書を作ってもらうべきなんだけど,\nお金もかかるし自分で遊ぶだけなのでオレオレ証明書でルート認証局を立ててみる.\nまずはOpenSSLでオレオレ証明書用の秘密鍵を作成する.\n普段はgenrsaを使ってるんだけどgenpkeyのほうが新しくてオススメされているみたいなのでそちらで作ってみる.\n(OpenSSLのサブコマンド多すぎ問題)\n## 以下すべてラズパイで実行 # ルート認証局用ディレクトリ(RootCA)で作業 $ mkdir RootCA \u0026amp;\u0026amp; cd RootCA # ルート認証局用の秘密鍵(RootCA-private-key.pem)を作成 $ openssl genpkey -algorithm RSA -out RootCA-private-key.pem 秘密鍵からルート認証局の正当性を主張するためのオレオレ証明書を作成する.\n質問の内容は適当でいい. はず\u0026hellip;\n# ルート認証局の秘密鍵(RootCA-private-key.pem)を使ったオレオレ証明書(RootCA.pem)の作成 $ openssl req -x509 -key RootCA-private-key.pem -out RootCA.pem You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter \u0026#39;.\u0026#39;, the field will be left blank. ----- Country Name (2 letter code) [AU]: State or Province Name (full name) [Some-State]: Locality Name (eg, city) []: Organization Name (eg, company) [Internet Widgits Pty Ltd]: Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []:RootCA # ここだけ入れてみる Email Address []: オレオレ証明書ができたので, これでルート認証局ができた.\nこの認証局は自分で自分の正当性を主張しているだけで公的な信頼は一切ないので注意.\n繰り返しになるけどちゃんとしたSSLサーバーを立てたいならここは公的な認証局にお願いするべき.\n中間証明書と中間認証局の作成 作成したルート認証局でそのままサーバー証明書を作ってもいいんだけど,\n今回は勉強のために中間認証局を立てて, その正当性を先程作ったルート認証局で証明する.\nまずは中間認証局用の秘密鍵を作成する.\n# 中間認証局用ディレクトリ(IntermediateCA)で作業 $ cd .. $ mkdir IntermediateCA \u0026amp;\u0026amp; cd IntermediateCA # 中間認証局用の秘密鍵(RootCA-private-key.pem)を作成 $ openssl genpkey -algorithm RSA -out IntermediateCA-private-key.pem この中間認証局の正当性をオレオレ証明書以外で証明するには他の認証局にお願いするしかないので,\n証明書署名要求(CSR)を作成する.\n質問の内容はこちらも適当でいい. はず\u0026hellip;\n# 中間認証局の秘密鍵(IntermediateCA-private-key.pem)から中間認証局のCSR(IntermediateCA-csr.pem)を作成 $ openssl req -new -key IntermediateCA-private-key.pem -out IntermediateCA-csr.pem You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter \u0026#39;.\u0026#39;, the field will be left blank. ----- Country Name (2 letter code) [AU]: State or Province Name (full name) [Some-State]: Locality Name (eg, city) []: Organization Name (eg, company) [Internet Widgits Pty Ltd]: Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []:IntermediateCA # ここだけ入れてみる Email Address []: Please enter the following \u0026#39;extra\u0026#39; attributes to be sent with your certificate request A challenge password []: An optional company name []: 作成した中間認証局のCSRをルート認証局に渡し, ルート認証局はそれに署名して中間認証局の正当性を証明する\u0026hellip;\nつもりだったのに怒られてしまった.\n# 中間認証局のCSR(IntermediateCA-csr.pem)をルート認証局に渡す $ mv IntermediateCA-csr.pem ../RootCA/ # ルート認証局の秘密鍵(RootCA-private-key.pem)とルート証明書(RootCA.pem)で # 中間認証局のCSR(IntermediateCA-csr.pem)に署名して中間証明書(IntermediateCA-certicifation.pem)を作成 # したかったけど怒られた... $ cd ../RootCA $ openssl ca -in IntermediateCA-csr.pem -out IntermediateCA-certicifation.pem -keyfile RootCA-private-key.pem -cert RootCA.pem Using configuration from /usr/lib/ssl/openssl.cnf ca: ./demoCA/newcerts is not a directory ./demoCA/newcerts: No such file or directory なんかディレクトリ./demoCA/newcertsが足りてないらしいので作って再挑戦.\n# ディレクトリを作成 $ mkdir -p demoCA/newcerts # 再挑戦したけどだめ $ openssl ca -in IntermediateCA-csr.pem -out IntermediateCA-certicifation.pem -keyfile RootCA-private-key.pem -cert RootCA.pem Using configuration from /usr/lib/ssl/openssl.cnf 1996152848:error:02001002:system library:fopen:No such file or directory:../crypto/bio/bss_file.c:72:fopen(\u0026#39;./demoCA/index.txt\u0026#39;,\u0026#39;r\u0026#39;) 1996152848:error:2006D080:BIO routines:BIO_new_file:no such file:../crypto/bio/bss_file.c:79: また怒られた.\nファイル./demoCA/index.txtが足りてないみたいなので空ファイルを作る.\n# 空ファイルを作成 $ touch ./demoCA/index.txt # 再挑戦したけどだめ $ openssl ca -in IntermediateCA-csr.pem -out IntermediateCA-certicifation.pem -keyfile RootCA-private-key.pem -cert RootCA.pem Using configuration from /usr/lib/ssl/openssl.cnf ./demoCA/serial: No such file or directory error while loading serial number 1995452432:error:02001002:system library:fopen:No such file or directory:../crypto/bio/bss_file.c:72:fopen(\u0026#39;./demoCA/serial\u0026#39;,\u0026#39;r\u0026#39;) 1995452432:error:2006D080:BIO routines:BIO_new_file:no such file:../crypto/bio/bss_file.c:79: またまた怒られた. つらい.\nファイル/demoCA/serialが足りてないらしい.\n# 空ファイルを作成 $ touch ./demoCA/serial # 再挑戦したけどだめ $ openssl ca -in IntermediateCA-csr.pem -out IntermediateCA-certicifation.pem -keyfile RootCA-private-key.pem -cert RootCA.pem Using configuration from /usr/lib/ssl/openssl.cnf unable to load number from ./demoCA/serial error while loading serial number 1995870224:error:0D066096:asn1 encoding routines:a2i_ASN1_INTEGER:short line:../crypto/asn1/f_int.c:140: またまたまた怒られた. キレそう.\n今度はシリアルナンバーが読めないよ的なエラーが出ているので, 適当な数字を入れてあげる.\n# 空ファイルに適当な数字を書き込む $ echo 00 \u0026gt;\u0026gt; ./demoCA/serial # いけた $ openssl ca -in IntermediateCA-csr.pem -out IntermediateCA-certicifation.pem -keyfile RootCA-private-key.pem -cert RootCA.pem Using configuration from /usr/lib/ssl/openssl.cnf Check that the request matches the signature Signature ok Certificate Details: Serial Number: 0 (0x0) Validity Not Before: Jun 1 14:24:19 2020 GMT Not After : Jun 1 14:24:19 2021 GMT Subject: countryName = AU stateOrProvinceName = Some-State organizationName = Internet Widgits Pty Ltd commonName = IntermediateCA X509v3 extensions: X509v3 Basic Constraints: CA:FALSE Netscape Comment: OpenSSL Generated Certificate X509v3 Subject Key Identifier: FF:A3:92:E0:F3:10:A9:28:AC:5F:4A:D2:BE:FD:D7:2B:19:EC:DD:02 X509v3 Authority Key Identifier: keyid:A9:96:E7:CD:B2:77:F2:8F:CA:4C:4A:E9:75:52:91:3E:6C:53:42:0B Certificate is to be certified until Jun 1 14:24:19 2021 GMT (365 days) Sign the certificate? [y/n]:y 1 out of 1 certificate requests certified, commit? [y/n]y Write out database with 1 new entries Data Base Updated こんどは成功した.\nちなみに./demoCA/index.txtは証明書発行の記録を残すためのファイルで,\n/demoCA/serialはシリアルナンバーを設定するためのファイルだったらしい.\n# index.txtの内容を確認 $ cat ./demoCA/index.txt V\t210601142419Z\t00\tunknown\t/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=IntermediateCA あとはルート認証局で作成した中間認証局の証明書(中間証明書)を中間認証局に渡す.\n# 中間証明書(IntermediateCA-certicifation.pem)を中間認証局に返す $ mv IntermediateCA-certicifation.pem ../IntermediateCA/ これで中間認証局の正当性をルート認証局が証明したことになる.\nサーバー証明書の作成 認証局の準備ができたので,\nいよいよHTTPSで使うためのサーバー証明書を作る.\nまずは中間認証局を立てたときと同じようにサーバーの正当性を証明するためのCSRを作成する.\n# サーバー用ディレクトリ(Server)で作業 $ cd .. $ mkdir Server \u0026amp;\u0026amp; cd Server # サーバー用の秘密鍵(Server-private-key.pem)を作成 $ openssl genpkey -algorithm RSA -out Server-private-key.pem # サーバーの秘密鍵(Server-private-key.pem)からCSR(Server-csr.pem)を作成 $ openssl req -new -key Server-private-key.pem -out Server-csr.pem You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter \u0026#39;.\u0026#39;, the field will be left blank. ----- Country Name (2 letter code) [AU]: State or Province Name (full name) [Some-State]: Locality Name (eg, city) []: Organization Name (eg, company) [Internet Widgits Pty Ltd]: Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []:uzimihsr.example.com # ここだけ入れてみる Email Address []: Please enter the following \u0026#39;extra\u0026#39; attributes to be sent with your certificate request A challenge password []: An optional company name []: サーバーのCSRを中間認証局に渡し,\n中間認証局は受け取ったCSRに署名してサーバー証明書を作成する.\n# サーバーのCSR(Server-csr.pem)を中間認証局に渡す $ mv Server-csr.pem ../IntermediateCA/ # 署名のための準備 $ cd ../IntermediateCA $ mkdir -p demoCA/newcerts $ touch ./demoCA/index.txt $ echo 01 \u0026gt; ./demoCA/serial # 中間認証局の秘密鍵(IntermediateCA-private-key.pem)と中間証明書(IntermediateCA-certicifation.pem)で # サーバーのCSR(Server-csr.pem)に署名してサーバー証明書(Server-certicifation.pem)を作成 $ openssl ca -in Server-csr.pem -out Server-certicifation.pem -keyfile IntermediateCA-private-key.pem -cert IntermediateCA-certicifation.pem Using configuration from /usr/lib/ssl/openssl.cnf Check that the request matches the signature Signature ok Certificate Details: Serial Number: 1 (0x1) Validity Not Before: Jun 1 14:49:49 2020 GMT Not After : Jun 1 14:49:49 2021 GMT Subject: countryName = AU stateOrProvinceName = Some-State organizationName = Internet Widgits Pty Ltd commonName = uzimihsr.example.com X509v3 extensions: X509v3 Basic Constraints: CA:FALSE Netscape Comment: OpenSSL Generated Certificate X509v3 Subject Key Identifier: 67:F1:62:B9:B4:FB:81:BD:E9:11:55:45:CF:43:D1:59:0B:66:D9:10 X509v3 Authority Key Identifier: keyid:FF:A3:92:E0:F3:10:A9:28:AC:5F:4A:D2:BE:FD:D7:2B:19:EC:DD:02 Certificate is to be certified until Jun 1 14:49:49 2021 GMT (365 days) Sign the certificate? [y/n]:y 1 out of 1 certificate requests certified, commit? [y/n]y Write out database with 1 new entries Data Base Updated # サーバーに証明書(Server-certicifation.pem)を渡す $ mv ./Server-certicifation.pem ../Server/ 以上の手順でサーバーの正当性を中間認証局が証明したことになる.\nnginxに証明書を持たせる サーバーの証明書ができたので,\nこれをnginxに持たせてHTTPSサーバーを建てる.\nまずはサーバー証明書の発行元の正当性を証明するために,\nルート証明書と中間証明書とサーバー証明書をくっつける.\n# サーバー証明書(Server-certicifation.pem), 中間証明書(IntermediateCA-certicifation.pem), ルート証明書(RootCA.pem)をくっつける(cert.pem) $ cd ../Server $ cat Server-certicifation.pem ../IntermediateCA/IntermediateCA-certicifation.pem ../RootCA/RootCA.pem \u0026gt; cert.pem 次にnginxにHTTPSサーバーとして動かすための設定を追加する.\n# nginx設定ファイルの作成 $ sudo vim /etc/nginx/conf.d/https.conf nginx.confは初期設定のまま.\nhttps.confで指定している場所にサーバーの証明書と秘密鍵を配置する.\nこれにより, nginxがHTTPSでリクエストを受けたときにクライアントにこの証明書を提示できるようになる.\n# サーバー証明書(cert.pem)と秘密鍵(Server-private-key.pem)の配置 $ sudo mkdir /etc/nginx/https $ sudo cp ./cert.pem /etc/nginx/https/ $ sudo cp ./Server-private-key.pem /etc/nginx/https/ ここまでできたら,\nnginxを再起動する.\n# nginxの設定を確認して再起動 $ sudo nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful $ sudo systemctl restart nginx ここまでできたら, Macのブラウザで https://\u0026lt;ラズパイのIP\u0026gt;/ を開く.\nしかしこのサーバー(nginx)から提示された証明書がMacに信頼されていないため, 警告が出てしまう.\n左上の鍵マークから証明書を表示して確認する.\nサーバー証明書(uzimihsr.example.com)が表示される.\nこの証明書は中間認証局(IntermediateCA)が発行者になっているが,\nこれが公的な認証局でないために信頼できない状態であることがわかる.\nnginxに持たせた証明書はサーバー証明書の他に中間証明書とルート証明書が連結してあるので,\nこれらの内容も確認できる.\n中間証明書(IntermediateCA)を確認する.\nこちらはルート認証局(RootCA)によって発行されているが,\nこれも公的な認証局でないために信頼されていない.\nルート証明書(RootCA)についても同様.\nルート認証局はどれもオレオレ証明書で自分の正当性を主張しているが,\nMacはこのルート認証局が信頼できる認証局のリストにないため,\nこの認証局が発行した証明書を信頼していない状態であることがわかる.\nこのままだとnginxの画面が開けないので,\nサーバー証明書をMacに信頼させるようにする.\nまずはChromeの証明書を開いている画面からサーバー証明書のアイコンをデスクトップにドラッグ\u0026amp;ドロップする.\nデスクトップに落ちた証明書をダブルクリックすると,\nキーチェーンアクセスが開く.\nサーバー証明書をダブルクリックすると詳細画面が開くので,\n信頼-\u0026gt;この証明書を信頼するときを常に信頼に変更してウィンドウを閉じる.\nこれでMacがこの証明書を信頼するようになった.\n再度Macのブラウザで https://\u0026lt;ラズパイのIP\u0026gt;/ を開く.\n今度はnginxのデフォルト画面が開けた.\nサーバー証明書を確認すると, 確かにこれが信頼されていることがわかる.\nやったぜ.\nOpenSSLとnginxでHTTPSサーバーを建てることができた.\n本当は他のHTTPSで提供されているサイトみたいにアドレスの左側に緑の鍵マークを表示させたいんだけど,\nChromeはちょっと厳しくて発行時にCNだけじゃなくてSANsもちゃんと設定した証明書じゃないといけないらしい\u0026hellip;\nとりあえず今回はサーバー証明書の練習が目的だったのでここまでで終わりにする.\nおわり 以上の手順でOpenSSLでサーバー証明書を作ってnginxにもたせてHTTPSサーバーを建てることができた.\n時間とレンタルサーバーを借りるお金の余裕があれば,\n無料で証明書を発行できるLet’s EncryptとかでちゃんとしたHTTPSサーバーを立ててみたい.\nおまけ 参考 オレオレ証明書とルート認証局の作成 https://www.openssl.org/docs/man1.1.0/man1/genpkey.html https://wiki.openssl.org/index.php/Command_Line_Utilities https://www.openssl.org/docs/man1.1.0/man1/x509.html 中間証明書と中間認証局の作成 サーバー証明書の作成 https://www.openssl.org/docs/man1.1.0/man1/req.html https://www.openssl.org/docs/man1.1.0/man1/ca.html nginxに証明書を持たせる https://nginx.org/en/docs/http/configuring_https_servers.html https://support.apple.com/ja-jp/guide/keychain-access/kyca11871/mac ","date":"2020-06-03T22:17:33+09:00","image":"/post/2020-06-03-server-certification-practice/sotochan.jpg","permalink":"/post/2020-06-03-server-certification-practice/","title":"nginxとOpenSSLでHTTPSサーバーを立てる"},{"content":"記念日だったりした 今月のそとちゃんはいろいろあったのでまとめ.\nまとめ 1周年 朝のルーティン ハンモック 元気が有り余っている 1周年 まずはなんといっても1周年記念日.\nそとちゃんは元号が令和に変わった日にうちに来たので,\n5/1で一緒に暮らし始めてからちょうど1年になった.\n本当の年齢も誕生日もわからないので, この日でたぶん4さいということにした.\n本当はたぶん4さいをちゃんと猫用ケーキでお祝いしたかったんだけど,\nコロナうんぬんで慌ただしい時期だったのでそとちゃんのお気に入りおやつ全部乗せをプレゼントした.\nInstagram\n余裕でカロリーオーバーなのでかわいそうだけど次の日はおやつ抜きだった\u0026hellip;\n来年こそはカロリーに配慮したおいしいケーキでお祝いしてあげたい.\n朝のルーティン 理由は謎だけど,\n先月の終わり頃から朝お腹の上に乗ってきておしりを見せてくれることが多くなった.\n朝 pic.twitter.com/1LinJL4LyT\n\u0026mdash; ずみし (@uzimihsr) April 30, 2020 朝だからおしり見せにきた pic.twitter.com/QmhZQlE5wy\n\u0026mdash; ずみし (@uzimihsr) May 18, 2020 おはようございます pic.twitter.com/zg3nprMEgC\n\u0026mdash; ずみし (@uzimihsr) May 19, 2020 朝だからおしり見せてくれる pic.twitter.com/8smYOnYCDi\n\u0026mdash; ずみし (@uzimihsr) May 28, 2020 まるいおしりがおまんじゅうみたいでかわいいけどちょっとくさい.\nでも猫がおしりを向けてくるのは信頼の証らしいので嬉しくもある.\nけどやっぱりちょっとくさい.\nうんちのにおいで起こされると気分があまりよくない.\nでもかわいいからしょうがないね.\nハンモック そとちゃんは窓から外の景色を眺めるのが好きなので,\n外が見やすくなるように窓に取り付けるタイプの猫用ハンモックを買ってみた.\n最初はやっぱり警戒しておやつで釣らないと乗ってくれなかったんだけど,\ninstagram\n1週間くらいしたら慣れて自分で乗るようになった.\nhttps://twitter.com/uzimihsr/status/1264456579127971841\n特に夜は車が走ってるのを見るのがトレンドらしい.\nInstagram\nうちはちょっと日当たりが悪いので, これで思う存分ひなたぼっこを楽しんでほしい.\nそろそろ暑くなってくるので熱中症がちょっとだけ心配.\n(ねこ, 暑さよりも居心地の良さを優先して動かないんだとか)\n元気が有り余っている 今月も健康面では特に問題なく, なんかやたら元気だった.\n動画は撮れてないけど,\nテレビ会議してるときは特に運動会とコンサートが始まってかなりうるさい.\nそとちゃん賢いのでかまってもらうためにわざとやってるまである(親馬鹿).\nあとは風呂待ちがエスカレートして洗濯物になったり,\n謎の落ち着きポイント pic.twitter.com/g9WyAtNlZ4\n\u0026mdash; ずみし (@uzimihsr) May 26, 2020 ねこが洗濯物になった pic.twitter.com/5d1aT4m46r\n\u0026mdash; ずみし (@uzimihsr) May 30, 2020 ふとんを攻撃したりしてた.\nふとん絶対バリバリにするマン(ねこ) pic.twitter.com/1Mzv2CWfcT\n\u0026mdash; ずみし (@uzimihsr) May 20, 2020 朝からふとんバリバリ\nしっぽの躍動感 pic.twitter.com/5ffteQh617\n\u0026mdash; ずみし (@uzimihsr) May 28, 2020 最近家にいても仕事でなかなか遊んであげられなかったり,\n新しいおもちゃも買ってあげられてないので刺激が足りないのかもしれない\u0026hellip;\nおわり そとちゃんは5月も元気だった.\n1年も一緒にいればお互いのことがわかってくると思ったらそんなことはなく,\n毎日新しい発見があるので本当にかわいくて面白い.\nちょっと運動が足りてない感じはするので, 6月はもっと遊ぶ時間を確保してあげて,\nあとは暑さ対策も気をつけてあげたい.\nおまけ ","date":"2020-05-31T21:57:23+09:00","image":"/post/2020-05-31-sotochan/sotochan.jpg","permalink":"/post/2020-05-31-sotochan/","title":"5月のそとちゃんまとめ(2020)"},{"content":"身元を証明する デジタル署名とデジタル証明書についても実際に触りながらまとめる.\nまとめ デジタル署名\n# メッセージ(message)のハッシュ値を秘密鍵(private-key.pem)で暗号化して署名(signature)を作成 $ openssl dgst -sha256 -sign private-key.pem -out signature message # 署名(signature)を公開鍵(public-key.pem)で復号化してメッセージ(message)のハッシュ値と照合 $ openssl dgst -sha256 -verify public-key.pem -signature signature message デジタル証明書\n# 秘密鍵(private-key.pem)から証明書署名要求(req.pem)を作成 # 証明書署名要求には公開鍵と, 自身の身元を証明する情報が含まれている $ openssl req -new -key private-key.pem -out req.pem # 証明書署名要求(req.pem)の内容確認 $ openssl req -in req.pem -text -verify -noout # 証明書署名要求(req.pem)に認証局の証明書(ca.pem)と秘密鍵(private-key-CA.pem)で署名して証明書を作成(cert.pem) # 証明書には証明書署名要求の公開鍵の内容と認証局の署名が含まれている $ openssl ca -policy policy_anything -in req.pem -out cert.pem -keyfile private-key-CA.pem -cert ca.pem # 証明書(cert.pem)の内容確認 $ openssl x509 -in cert.pem -noout -text # 証明書(cert.pem)を信頼できる認証局の証明書(ca.pem)で検証する $ openssl verify -CAfile ca.pem cert.pem # 証明書(cert.pem)に含まれる公開鍵の確認 $ openssl x509 -in cert.pem -pubkey -noout 環境 Raspberry Pi 3 Model B+ OSはRaspbian(10.0) openssl OpenSSL 1.1.1c 28 May 2019 ラズパイの初期装備 もくじ デジタル署名 デジタル証明書 デジタル署名 デジタル証明書の前に, デジタル署名について確認する.\nデジタル署名とは次のような流れでデータの送信者が秘密鍵を持つ本人であることとそれが途中で改ざんされていないことを証明する仕組み.\n送信側で秘密鍵と公開鍵を作成し, 受信側に公開鍵を渡す 送信側でメッセージを作成し, それをハッシュ化する 送信側で得られたハッシュ値を秘密鍵で暗号化(署名)する 送信側はメッセージ, 署名, 公開鍵を受信側に渡す 受信側は受け取った署名を公開鍵で復号化し, 受け取ったメッセージをハッシュ化したものと照合する また, これを実現するためデジタル署名には次の特徴がある.\n秘密鍵でしか暗号化できない 公開鍵で復号化すると元のメッセージに戻る 実際にやってみる.\nまずは送信側で秘密鍵と公開鍵を作成し, メッセージを暗号化(署名)する.\n# 2つのディレクトリを送信側(dirX), 受信側(dirY)に見立てて作業 $ mkdir dirX dirY # 送信側(dirX)で秘密鍵(private-key.pem)と公開鍵(public-key.pem)を作成して公開鍵を受信側(dirY)に渡す $ cd dirX $ openssl genrsa -out private-key.pem $ openssl rsa -in private-key.pem -pubout -out public-key.pem $ cp ./public-key.pem ../dirY 次にメッセージを作成し, ハッシュ化する.\n# メッセージ(message)を作成してSHA-256でハッシュ化(hashed-message) $ echo qwerty \u0026gt; message $ openssl sha256 -out hashed-message message $ cat hashed-message SHA256(message)= 9ceece10cf8b97d1f1924dae5d14c137fd144ce999ede85f48be6d7582e2dd23 ハッシュ値を秘密鍵で暗号化(署名)する.\n# ハッシュ値(hashed_message)を秘密鍵(private-key.pem)で暗号化して署名(signature)を作成 $ openssl rsautl -sign -in hashed-message -out signature -inkey private-key.pem $ cat signature ���������������� メッセージ, 署名を受信側に渡す.\n# メッセージ(message), 署名(signature)を受信側(dirY)に渡す $ cp ./message ../dirY $ cp ./signature ../dirY/ $ cd ../dirY $ ls message public-key.pem signature 受信側で署名を復号化して, メッセージをハッシュ化したものと照合する.\n一致すればメッセージが改ざんされておらず, 送り主が秘密鍵の持ち主であることを証明できる.\n# 署名(signature)を公開鍵(public-key.pem)で復号化(signature-verify) $ openssl rsautl -verify -in signature -out signature-verify -inkey public-key.pem -pubin # メッセージ(message)をハッシュ化(hashed-message-verify) $ openssl sha256 -out hashed-message-verify message # 復号化した署名(signature-verify)とメッセージのハッシュ値(hashed-message-verify)を照合 $ cat signature-verify SHA256(message)= 9ceece10cf8b97d1f1924dae5d14c137fd144ce999ede85f48be6d7582e2dd23 $ cat hashed-message-verify SHA256(message)= 9ceece10cf8b97d1f1924dae5d14c137fd144ce999ede85f48be6d7582e2dd23 $ diff signature-verify hashed-message-verify メッセージのハッシュと署名を復号化した値が一致する組み合わせを作れるのは公開鍵に対応する秘密鍵を持つ者だけなので,\n仮にメッセージが改ざんされたり, 同じメッセージが別の秘密鍵で署名された場合は\n受信側で照合したときに一致せず, 異常を検知することができる.\n以上の手順を踏むことで, 通信時のなりすましや改ざん, 事後否認を防ぐことができる.\nまた, ハッシュ計算と署名を1つのコマンドで実行することもできる.\n# メッセージ(message)のハッシュ値を秘密鍵(private-key.pem)で暗号化して署名(signature)を作成 $ openssl dgst -sha256 -sign private-key.pem -out signature message # 署名(signature)を公開鍵(public-key.pem)で復号化してメッセージ(message)のハッシュ値と照合 $ openssl dgst -sha256 -verify public-key.pem -signature signature message デジタル証明書 盗聴を防ぐための方法として用いる公開鍵暗号方式やハイブリッド暗号方式,\nそしてなりすまし, 改ざん, 事後否認を防ぐために用いるデジタル署名だが,\nこれらはすべて受信側が受け取った公開鍵を信頼できるという条件の上で成り立っている.\n仮に公開鍵を受け取る際にそれが悪意のある第三者によってすり替えられていた場合,\nこれらの仕組みが全く意味を持たなくなってしまう(公開鍵の信頼性の問題).\nこの問題を解決し, 公開鍵の正当性を証明するのがデジタル証明書.\n以下の流れで証明書の作成と検証を行う.\n自分の身分を証明したい人(A)が秘密鍵と公開鍵を用意する. Aは自分の公開鍵と自分の身元を証明できる情報を用意(証明書署名要求)して認証局(CA)に送る. CAはAの証明書署名要求からAの身元を確認した後それらの情報にCA自身の秘密鍵で署名して証明書を作成し, Aに渡す. Aは通信したい人(B)に自分の証明書を渡す. BはCA自体の証明書(公開鍵)でAの証明書を検証する. また, この仕組みを実現するために証明書には次の性質がある.\nAの証明書署名要求はA本人しか作れない 証明書には身元が証明される人の公開鍵情報とそれに署名した認証局の情報が含まれる 信頼している認証局が署名した証明書であれば信頼性が保証される 認証局自身の信頼性はさらに上位の認証局が発行する証明書で保証する 実際にやってみて確認する.\nまずはAとCAの秘密鍵, 公開鍵を用意する.\n# 3つのディレクトリを通信者(dirA, dirB), 認証局(dirCA)に見立てて作業する $ mkdir dirA dirB dirCA # CAとAはそれぞれ秘密鍵を持っている状態 $ cd dirCA $ openssl genrsa -out private-key-CA.pem $ openssl rsa -in private-key-CA.pem -pubout -out public-key-CA.pem $ cd ../dirA $ openssl genrsa -out private-key-A.pem $ openssl rsa -in private-key-A.pem -pubout -out public-key-A.pem 次にAは自分の公開鍵と身元を証明する情報を合わせたデータ(証明書署名要求)を作成し,\nCAに渡す.\n# Aの秘密鍵(private-key-A.pem)から証明書署名要求(req.pem)を作成 # 実際は秘密鍵から公開鍵を取り出して使っている(秘密鍵そのものを渡すわけではない) $ openssl req -new -key private-key-A.pem -out req.pem You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter \u0026#39;.\u0026#39;, the field will be left blank. ----- # 以下, 身元を証明するための情報を入力する Country Name (2 letter code) [AU]:JP #国 State or Province Name (full name) [Some-State]:Tokyo # 都道府県 Locality Name (eg, city) []: # 市区町村 Organization Name (eg, company) [Internet Widgits Pty Ltd]: # 組織名 Organizational Unit Name (eg, section) []: # 部署名 Common Name (e.g. server FQDN or YOUR name) []:uzimihsr.example.com # 名前 Email Address []:example@mail.com # メールアドレス Please enter the following \u0026#39;extra\u0026#39; attributes to be sent with your certificate request A challenge password []: An optional company name []: $ cat req.pem -----BEGIN CERTIFICATE REQUEST----- MIICxjCCAa4CAQAwgYAxCzAJBgNVBAYTAkpQMQ4wDAYDVQQIDAVUb2t5bzEhMB8G ... cH32EBVTnecz4nSPVYHwr9xcgKi+i0ol4ea0lMNWz5m0O0dYOc6H0Qqc -----END CERTIFICATE REQUEST----- # 証明書署名要求(req.pem)をCAに渡す $ cp req.pem ../dirCA/ CAは受け取ったAの証明書署名要求の内容を確認する.\n# 証明書署名要求(req.pem)の内容を検証する $ cd ../dirCA $ openssl req -in req.pem -text -verify -noout verify OK Certificate Request: Data: # Aの公開鍵と身元の情報 Version: 1 (0x0) Subject: C = JP, ST = Tokyo, O = Internet Widgits Pty Ltd, CN = uzimihsr.example.com, emailAddress = example@mail.com Subject Public Key Info: Public Key Algorithm: rsaEncryption RSA Public-Key: (2048 bit) Modulus: 00:b7:51:7c:41:9c:c9:f2:4d:c0:a8:43:bb:96:2a: ... 0c:d7 Exponent: 65537 (0x10001) Attributes: a0:00 Signature Algorithm: sha256WithRSAEncryption 26:88:97:d7:c4:57:da:24:1b:d5:b4:e2:e8:82:23:b5:1c:e0: ... 87:d1:0a:9c 証明書署名要求の内容に問題がなければ署名してAの証明書を作る\u0026hellip;前に認証局もそれ自身の身元を証明する必要があるので,\n自己証明証明書(オレオレ証明書)を作ってCAを認証局として動かすための設定をする.\n# CA自身の身元を証明し鍵(private-key-CA.pem)の正当性を保証するためのオレオレ証明書(ca.pem)を作成 # こちらも内部で秘密鍵から公開鍵を取り出して使用している(証明書に秘密鍵を埋め込むわけではない) $ openssl req -x509 -key private-key-CA.pem -out ca.pem You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter \u0026#39;.\u0026#39;, the field will be left blank. ----- # その場しのぎの証明書なので何も入れなくてもOK Country Name (2 letter code) [AU]: State or Province Name (full name) [Some-State]: Locality Name (eg, city) []: Organization Name (eg, company) [Internet Widgits Pty Ltd]: Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []: Email Address []: # 証明書に署名するために必要な設定 $ mkdir -p demoCA/newcerts $ touch demoCA/index.txt $ echo 01 \u0026gt; demoCA/serial CAはAの証明書署名要求に署名してAの証明書を作成し, Aに渡す.\nこれによりAの身元と公開鍵の正当性をCAが保証することになる.\n# Aの証明書署名要求(req.pem)にCA自身の秘密鍵(private-key-CA.pem)と証明書(ca.pem)を使って署名する(cert-A.pem) $ openssl ca -policy policy_anything -in req.pem -out cert-A.pem -keyfile private-key-CA.pem -cert ca.pem Using configuration from /usr/lib/ssl/openssl.cnf Check that the request matches the signature Signature ok Certificate Details: Serial Number: 1 (0x1) Validity Not Before: May 30 09:15:17 2020 GMT Not After : May 30 09:15:17 2021 GMT Subject: countryName = JP stateOrProvinceName = Tokyo organizationName = Internet Widgits Pty Ltd commonName = uzimihsr.example.com emailAddress = example@mail.com X509v3 extensions: X509v3 Basic Constraints: CA:FALSE Netscape Comment: OpenSSL Generated Certificate X509v3 Subject Key Identifier: F2:CC:D3:94:F9:57:AE:F3:4E:A9:12:1F:15:29:87:8C:58:4B:6C:56 X509v3 Authority Key Identifier: keyid:57:A6:BC:47:C9:76:6C:E3:93:48:D7:09:9E:03:8A:86:3F:A5:08:80 ... Sign the certificate? [y/n]:y 1 out of 1 certificate requests certified, commit? [y/n]y Write out database with 1 new entries Data Base Updated # 証明書をAに渡す $ cp cert-A.pem ../dirA/ Aは通信を行いたい相手(B)に自分の証明書を渡す.\n# Bに証明書を渡す $ cd ../dirA $ cp cert-A.pem ../dirB BはAの証明書の内容を確認する.\nBはCAを信頼しているので,\nAの証明書にCAが署名していることを確認できればこれを信頼し, 証明書から取り出したAの公開鍵を安心して利用することができる.\n# Aの証明書(cert-A.pem)の内容を確認する $ cd ../dirB $ openssl x509 -in cert-A.pem -noout -text Certificate: Data: Version: 3 (0x2) Serial Number: 1 (0x1) Signature Algorithm: sha256WithRSAEncryption Issuer: C = AU, ST = Some-State, O = Internet Widgits Pty Ltd # 認証局(CA)の情報 Validity Not Before: May 30 09:15:17 2020 GMT Not After : May 30 09:15:17 2021 GMT Subject: C = JP, ST = Tokyo, O = Internet Widgits Pty Ltd, CN = uzimihsr.example.com, emailAddress = example@mail.com # Aの身元情報 Subject Public Key Info: Public Key Algorithm: rsaEncryption RSA Public-Key: (2048 bit) Modulus: # Aの公開鍵情報 00:b7:51:7c:41:9c:c9:f2:4d:c0:a8:43:bb:96:2a: ... 0c:d7 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Basic Constraints: CA:FALSE Netscape Comment: OpenSSL Generated Certificate X509v3 Subject Key Identifier: F2:CC:D3:94:F9:57:AE:F3:4E:A9:12:1F:15:29:87:8C:58:4B:6C:56 X509v3 Authority Key Identifier: keyid:57:A6:BC:47:C9:76:6C:E3:93:48:D7:09:9E:03:8A:86:3F:A5:08:80 Signature Algorithm: sha256WithRSAEncryption 39:a3:7f:a3:dd:9c:ff:21:b7:b3:1b:44:07:c1:38:e0:ae:7a: ... bc:d0:36:70 # 信頼している認証局(CA)の証明書(ca.pem)を使ってAの証明書(cert-A.pem)が正しいか検証する $ openssl verify -CAfile ../dirCA/ca.pem cert-A.pem cert-A.pem: OK # 検証が成功したので証明書からAの公開鍵を取り出す $ openssl x509 -in cert-A.pem -pubkey -noout -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt1F8QZzJ8k3AqEO7liqz ... 1wIDAQAB -----END PUBLIC KEY----- # 以降はAの公開鍵を信頼して通信する Bが信頼できる証明書はBが信頼している認証局とA本人によってのみ作成できるので,\n悪意のある第三者がAになりすましてBに自身の公開鍵を渡すことはできない.\nしたがって, 証明書を使うことで公開鍵の正当性を保証することができる.\n以上の手順で公開鍵の信頼性を保証することができた.\nやることが多くて混乱するけど,\n要は信頼できる認証局が相手の身元を保証していれば自分も相手を信頼する, という仕組み.\n今回は認証局(CA)の身元を自身で保証したけど(オレオレ証明書),\n実際は上位の認証局がその正当性を保証する.\nその上位の認証局の正当性はさらに上位の認証局が保証し\u0026hellip; というように,\n認証局の保証は木構造になっている.\n最上位の認証局の正当性は自身の証明書では保証できないので,\n公的に信頼できる政府関連の機関などが最上位の認証局を努めている.\n以上が公開鍵基盤(PKI)の仕組み.\nおわり 公開鍵暗号方式, ハイブリッド暗号方式に続いてデジタル証明書を実際に試してみた.\n証明書署名要求から証明書の作成までの手順も確認できたので,\n機会があればオレオレ証明書でSSL通信したり, クライアント認証とかを試してみたい.\nおまけ 参考 デジタル署名 https://www.openssl.org/docs/man1.1.0/man1/dgst.html https://www.openssl.org/docs/man1.1.0/man1/rsautl.html デジタル証明書 https://www.openssl.org/docs/man1.1.0/man1/req.html https://www.openssl.org/docs/man1.1.0/man1/ca.html https://www.openssl.org/docs/man1.1.0/man1/x509.html https://www.openssl.org/docs/man1.1.0/man1/verify.html ","date":"2020-05-30T19:58:15+09:00","image":"/post/2020-05-30-certification-practice/sotochan.jpg","permalink":"/post/2020-05-30-certification-practice/","title":"OpenSSLでデジタル証明書を試す"},{"content":"欠点を補いあう 共通鍵暗号方式とハイブリッド暗号方式についても実際に触りながらまとめる.\nまとめ 共通鍵でのデータの暗号化と復号化\n# 32bytesの乱数パスワードファイル(password)の作成 $ openssl rand -base64 -out password 32 # 平文データ(data)を公開鍵(password)を使ってaes256で暗号化(encrypted-data) $ openssl enc -aes256 -in data -out encrypted-data -pass file:password -base64 # 暗号データ(encrypted-data)を公開鍵(password)を使ってaes256で復号化(decrypted-data) $ openssl enc -d -aes256 -in encrypted-data -out decrypted-data -pass file:password -base64 ハイブリッド暗号方式\n# 秘密鍵(private-key.pem)と公開鍵(public-key.pem)の作成 # 作成した公開鍵はもう片方に渡す $ openssl genrsa -out private-key.pem $ openssl rsa -in private-key.pem -pubout -out public-key.pem # 共通鍵(password)の作成 (秘密鍵を持たない方が実行) $ openssl rand -base64 -out password 32 # 公開鍵(public-key.pem)を使って共通鍵(password)を暗号化(encrypted-password) (秘密鍵を持たない方が実行) # 暗号化した共通鍵をもう片方に渡す $ openssl rsautl -encrypt -in password -out encrypted-password -inkey public-key.pem -pubin # 秘密鍵(private-key.pem)を使って暗号化された共通鍵(encrypted-password)を復号化(decrypted-password) (秘密鍵を持つ方が実行) $ openssl rsautl -decrypt -in encrypted-password -out decrypted-password -inkey private-key.pem # 以降は共通鍵で暗号化したデータをやりとりする 環境 Raspberry Pi 3 Model B+ OSはRaspbian(10.0) openssl OpenSSL 1.1.1c 28 May 2019 ラズパイの初期装備 もくじ 共通鍵暗号方式 公開鍵暗号方式と共通鍵暗号方式の比較 ハイブリッド暗号方式 共通鍵暗号方式 共通鍵暗号方式と言っても,\n(誤解を恐れずに言えば)パスワードでデータを暗号化/復号化する方式のこと.\n共通鍵暗号方式の通信の簡単な流れとしては\n送信側と受信側で事前に同じ共通鍵(パスワード)を持っておく 送信側は平文データを共通鍵で暗号化する 送信側は受信側に暗号データを渡す 受信側は暗号データを共通鍵で復号化する たったこれだけ.\n暗号化/復号化に同じ鍵を使うことが大きな特徴.\n実際にやってみる.\nまずはじめに共通鍵(パスワード)を作成して送信側と受信側で共有する.\n# ディレクトリを送信側(dirA)と受信側(dirB)に見立てて作業 $ mkdir dirA dirB $ cd dirA # 共通鍵(password)を作成 # 本当はこんな適当な値ではなく乱数が望ましい $ echo 1234567 \u0026gt; password $ cat password 1234567 # 共通鍵(password)を受信側(dirB)に共有 $ cp password ../dirB/ $ ls password $ ls ../dirB password 次に平文データを共通鍵で暗号化する.\nこれにより, 共通鍵がわからない場合はデータの内容を読むことができなくなる.\n# 送信側(dirA)で作業 $ pwd /path/to/dirA # 平文データ(data.txt)を作成 $ echo abcdefg \u0026gt; data.txt # 暗号化アルゴリズム(aes256)を使って平文データ(data.txt)を暗号化(encrypted-data.txt) # アルゴリズムが非推奨のものなので警告が出ているがとりあえずはOK $ openssl enc -aes256 -in data.txt -out encrypted-data.txt -pass file:password *** WARNING : deprecated key derivation used. Using -iter or -pbkdf2 would be better. $ ls data.txt encrypted-data.txt password # 暗号化されているので読めない $ cat encrypted-data.txt Salted__aGo�r�ʵ�3�Z��ċ�D 暗号データを受信側に渡し,\n受信側で共通鍵を使って復号化する.\n# 暗号データ(encrypted-data.txt)を受信側(dirB)に渡す $ pwd /path/to/dirA $ cp encrypted-data.txt ../dirB/ # 受信側(dirB)で作業 $ cd ../dirB $ ls encrypted-data.txt password # 暗号データ(encrypted-data.txt)を共通鍵(password)で復号化(decrypted-data.txt) $ openssl enc -d -aes256 -in encrypted-data.txt -out decrypted-data.txt -pass file:password *** WARNING : deprecated key derivation used. Using -iter or -pbkdf2 would be better. $ cat decrypted-data.txt abcdefg こんな感じでデータの暗号化と復号化ができる.\n重要なのは, 送信側と受信側の間で暗号データの他に共通鍵の受け渡しをしていること.\n共通鍵暗号方式では同じ鍵で暗号化と復号化が行われるため,\n通信が傍受され共通鍵が流出した場合は簡単に平文データが取り出されてしまう.\n(いわゆる鍵配送問題)\n公開鍵暗号方式と共通鍵暗号方式の比較 公開鍵暗号方式と共通鍵暗号方式の大きな違いとしては\n公開鍵暗号方式が暗号化/復号化を 別々の鍵 で行うのに対し,\n共通鍵暗号方式では暗号化/復号化を 同じ鍵 で行うこと.\nこれらの特徴を比較すると次のようになる.\n共通鍵暗号方式 公開鍵暗号方式よりも暗号化/復号化が高速 アルゴリズムが簡単なため 通信を傍受されると無力 公開鍵暗号方式 暗号化/復号化が遅い 通信を傍受されても問題ない これらの特徴をうまく組み合わせて高速で安全な通信を行うのがハイブリッド暗号方式.\nハイブリッド暗号方式 ハイブリッド暗号方式の大きな特徴は,\n共通鍵そのものを公開鍵暗号方式で渡すこと.\n主な流れはこんな感じ.\n送信側で共通鍵を作成 受信側で秘密鍵と公開鍵を作成, 送信側に公開鍵を渡す 送信側は受け取った公開鍵で共通鍵を暗号化して受信側に渡す 受信側は暗号化された共通鍵を秘密鍵で復号化 以降は2者間で共通鍵で暗号化したデータをやり取りする 最初の1回だけ公開鍵暗号方式で共通鍵を渡した後は高速な共通鍵暗号方式を使うことができるので,\n高速で安全にデータをやり取りするためのSSL/TLS通信では公開鍵暗号方式単体ではなくこちらが使用されている.\n実際にやってみる.\nまずは送信側で共通鍵を作成する.\n# ディレクトリを送信側(dir1)と受信側(dir2)に見立てて作業 $ mkdir dir1 dir2 # 送信側(dir1)で共通鍵(password)を作成 # 今回はちゃんと32bytesの乱数で作成する $ cd dir1 $ openssl rand -base64 -out password 32 $ cat password VAJhXVUt7aRxfFs5ba7SNkjWyIqOaI8E10t0tnmF7as= 次に受信側で秘密鍵と公開鍵を作成して公開鍵を送信側に渡す.\n# 受信側(dir2)で秘密鍵(private-key.pem)と公開鍵(public-key.pem)を作成, 送信側(dir1)に公開鍵を渡す $ cd ../dir2 $ openssl genrsa -out private-key.pem $ openssl rsa -in private-key.pem -pubout -out public-key.pem $ cp ./public-key.pem ../dir1/ # 公開鍵を渡す 送信側は受け取った公開鍵で共通鍵を暗号化して受信側に渡す.\n# 送信側(dir1)は受け取った公開鍵(public-key.pem)で共通鍵(password)を暗号化(encrypted-password)して受信側に渡す $ cd ../dir1 $ openssl rsautl -encrypt -in password -out encrypted-password -inkey public-key.pem -pubin $ cat encrypted-password ��ʞ��... $ cp ./encrypted-password ../dir2/ # 暗号化された共通鍵を渡す 最後に受信側は暗号化された共通鍵を秘密鍵で復号化する.\n# 受信側(dir2)は暗号化された共通鍵(encrypted-password)を秘密鍵(private-key.pem)で復号化(decrypted-password) $ cd ../dir2 $ openssl rsautl -decrypt -in encrypted-password -out decrypted-password -inkey private-key.pem $ cat decrypted-password VAJhXVUt7aRxfFs5ba7SNkjWyIqOaI8E10t0tnmF7as= 以上の手順で送信側, 受信側双方に共通鍵が存在する状態になったので,\n以降は2者間で共通鍵暗号方式で通信する.\n# 以降は2者間で共通鍵で暗号化したデータをやり取りする $ echo hello \u0026gt; file1 $ openssl enc -aes256 -in file1 -out encrypted-file1 -pass file:decrypted-password -base64 $ cp ./encrypted-file1 ../dir1/ # 暗号データを渡す $ cd ../dir1 $ openssl enc -d -aes256 -in encrypted-file1 -out decrypted-file1 -pass file:password -base64 $ cat decrypted-file1 hello $ echo world \u0026gt; file2 $ openssl enc -aes256 -in file2 -out encrypted-file2 -pass file:password -base64 $ cp ./encrypted-file2 ../dir2/ # 暗号データを渡す $ cd ../dir2 $ openssl enc -d -aes256 -in encrypted-file2 -out decrypted-file2 -pass file:decrypted-password -base64 $ cat decrypted-file2 world 重要なのは暗号データの他に2者間でやり取りされるデータが 公開鍵, 公開鍵で暗号化された共通鍵のみ であること.\n公開鍵暗号方式の性質として公開鍵では暗号化された共通鍵の復号化ができず,\n暗号化された状態の共通鍵では暗号データを復号化できないため,\n仮に悪意のある第三者がこれらの通信を傍受してもやりとりされる平文データの内容を読むことはできない(秘密が守られる).\nおわり 公開鍵暗号方式に続いて共通鍵暗号方式とハイブリッド暗号方式を実際に試してみた.\nこれでも十分そうに見えるけど, まだ公開鍵の信頼性にまつわる問題があるので次はデジタル証明書について触ってみたい.\nおまけ 参考 共通鍵暗号方式 https://www.openssl.org/docs/man1.1.0/man1/enc.html https://www.openssl.org/docs/man1.1.0/man1/openssl.html 公開鍵暗号方式と共通鍵暗号方式の比較 アルゴリズム図鑑 絵で見てわかる26のアルゴリズム ほんとにわかりやすい ハイブリッド暗号方式 https://www.openssl.org/docs/man1.1.0/man1/rand.html ","date":"2020-05-24T17:09:44+09:00","image":"/post/2020-05-24-hybrid-encryption-practice/sotochan.jpg","permalink":"/post/2020-05-24-hybrid-encryption-practice/","title":"OpenSSLで共通鍵暗号方式とハイブリッド暗号方式を試す"},{"content":"暗号わかんね 公開鍵暗号方式についてよくわかってないままだったので実際に触りながら自分なりにまとめる.\nまとめ 秘密鍵と公開鍵の作成\n# 秘密鍵(private-key.pem)を作成 ## パスフレーズなし $ openssl genrsa -out private-key.pem ## パスフレーズあり(aes256で暗号化) $ openssl genrsa -out private-key.pem -aes256 # 秘密鍵(private-key.pem)から公開鍵(public-key.pem)を作成 $ openssl rsa -in private-key.pem -pubout -out public-key.pem データの暗号化と復号化\n# 公開鍵(public-key.pem)を使ってデータ(data.txt)を暗号化(encrypted-data.txt) $ openssl rsautl -encrypt -in data.txt -out encrypted-data.txt -inkey public-key.pem -pubin # 秘密鍵(private-key.pem)を使って暗号データ(encrypted-data.txt)を復号化(decrypted-data.txt) $ openssl rsautl -decrypt -in encrypted-data.txt -out decrypted-data.txt -inkey private-key.pem 秘密鍵のパスフレーズ\n# 秘密鍵(private-key.pem)にパスフレーズをつける(private-key-with-pass-phrase.pem) $ openssl rsa -in private-key.pem -out private-key-with-pass-phrase.pem -aes256 # 秘密鍵(private-key-with-pass-phrase.pem)のパスフレーズを外す(private-key-without-pass-phrase.pem) $ openssl rsa -in private-key-with-pass-phrase.pem -out private-key-without-pass-phrase.pem 環境 Raspberry Pi 3 Model B+ OSはRaspbian(10.0) openssl OpenSSL 1.1.1c 28 May 2019 ラズパイの初期装備 もくじ 秘密鍵と公開鍵の性質 秘密鍵と公開鍵を作る 公開鍵で暗号化する 秘密鍵で復号化する パスフレーズをつける 秘密鍵と公開鍵の性質 まず秘密鍵と公開鍵の性質について.\n数学が苦手なので詳細なアルゴリズムとか数式とかは抜きにして次の性質がある前提で進める.\n秘密鍵と公開鍵は鍵生成アルゴリズムによって作成される数値 公開鍵から秘密鍵を逆算することはできない 公開鍵を用いてデータを暗号化する 暗号化されたデータは秘密鍵によってのみ復号化できる とはいえ, 言葉だとよくわかんないので実際に作ってみるのが手っ取り早い.\n秘密鍵と公開鍵を作る opensslを使ってまずは秘密鍵と公開鍵を作ってみる.\n秘密鍵と公開鍵は鍵生成アルゴリズムによって作成される のだが,\n今回は鍵生成アルゴリズムとしてRSAを使う.\nアルゴリズムの詳細は省くけど, 素数が関係しているくらいは覚えておいてもいいはず.\nRSAの秘密鍵の作成自体はかんたんで, コマンド1行でできる.\n# 適当なディレクトリを作って作業 $ mkdir dirA $ cd dirA # パスフレーズなしの秘密鍵(private-key.pem)を作成する $ openssl genrsa -out private-key.pem Generating RSA private key, 2048 bit long modulus (2 primes) .........................................+++++ ...............................+++++ e is 65537 (0x010001) # 秘密鍵が生成される $ ls private-key.pem # そのまま表示しても読めない $ cat private-key.pem -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEA58HW0I+9qZiHopidx7pUuxNzVuobgzgp7OswlvgSHn2/BbKL ... fSy2dXrjU08OXro9YHz0d6XESG3feSinK0ND0sbsrKTD+4j7fetk -----END RSA PRIVATE KEY----- 次に秘密鍵の内容を確認してみる.\n秘密鍵には公開鍵の情報と鍵を作成したときの情報とかが入ってる.\n(例: modulusが公開鍵の情報で, prime1とかprime2とあるのが鍵生成時に使用した素数の情報)\n# 秘密鍵(private-key.pem)の中身を確認する $ openssl rsa -in private-key.pem -text -noout RSA Private-Key: (2048 bit, 2 primes) modulus: 00:e7:c1:d6:d0:8f:bd:a9:98:87:a2:98:9d:c7:ba: 54:bb:13:73:56:ea:1b:83:38:29:ec:eb:30:96:f8: 12:1e:7d:bf:05:b2:8b:c5:ff:33:fb:b1:7d:be:a6: 7b:ec:62:5c:b2:d3:dd:b7:34:ec:5f:b2:1a:56:63: 27:d4:f4:e0:7d:10:2b:29:8b:16:6e:f1:ce:7b:73: 31:67:39:ca:cf:df:d3:fb:14:15:94:85:80:f4:2a: ee:c6:93:79:ff:81:09:51:55:29:14:e7:d3:dd:87: d4:82:2f:c2:80:5c:e3:89:60:c6:a7:9a:43:b3:0d: 33:98:d1:05:1c:20:38:a4:dd:19:39:b8:0b:d1:6e: 84:8f:e9:55:df:fd:70:16:c5:f8:5d:66:6a:03:13: 36:7b:ab:e1:7e:30:7e:76:6b:e0:50:2d:cb:ad:59: 9c:db:66:ba:2e:9d:61:50:ad:0f:2f:fe:22:7f:bb: ef:91:f5:70:02:bd:71:2f:6a:93:2f:db:85:fd:3e: 28:cf:39:d1:d4:39:32:e8:c7:a0:3b:10:98:36:44: 69:d3:15:57:bf:59:53:0c:88:01:02:78:9f:69:f6: 1b:c2:20:5a:e0:0e:b2:1f:ed:10:c1:bf:9d:d0:17: 9d:0a:7a:25:8d:84:bc:39:f1:1b:74:11:47:e0:68: 03:21 publicExponent: 65537 (0x10001) privateExponent: 79:5d:79:31:1f:15:23:8b:4c:fc:49:0f:d7:58:2c: ... prime1: 00:fc:4f:d2:2b:d8:79:fa:6d:ca:c4:3f:b1:fe:67: ... prime2: 00:eb:25:19:b7:ca:02:f0:9a:09:ea:7f:19:03:40: ... exponent1: 00:e8:69:5e:5f:a4:f8:37:06:0b:50:da:9b:4a:8c: ... exponent2: 22:f6:11:2c:d2:4c:3d:99:a9:7f:c4:05:e4:05:db: ... coefficient: 4a:9f:3d:f5:6f:b6:83:02:f0:57:47:03:c3:d5:8c: ... 次に秘密鍵のデータから公開鍵を取り出してみる.\n公開鍵の中身が秘密鍵の中身のmodulusと同じになっていることが確認できる.\n# 秘密鍵から公開鍵(public-key.pem)を出力する $ openssl rsa -in private-key.pem -pubout -out public-key.pem writing RSA key # 公開鍵が生成される $ ls private-key.pem public-key.pem # 公開鍵もそのままでは読めない $ cat public-key.pem -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA58HW0I+9qZiHopidx7pU ... IQIDAQAB -----END PUBLIC KEY----- # 公開鍵の中身を確認する $ openssl rsa -in public-key.pem -text -noout -pubin RSA Public-Key: (2048 bit) Modulus: 00:e7:c1:d6:d0:8f:bd:a9:98:87:a2:98:9d:c7:ba: 54:bb:13:73:56:ea:1b:83:38:29:ec:eb:30:96:f8: 12:1e:7d:bf:05:b2:8b:c5:ff:33:fb:b1:7d:be:a6: 7b:ec:62:5c:b2:d3:dd:b7:34:ec:5f:b2:1a:56:63: 27:d4:f4:e0:7d:10:2b:29:8b:16:6e:f1:ce:7b:73: 31:67:39:ca:cf:df:d3:fb:14:15:94:85:80:f4:2a: ee:c6:93:79:ff:81:09:51:55:29:14:e7:d3:dd:87: d4:82:2f:c2:80:5c:e3:89:60:c6:a7:9a:43:b3:0d: 33:98:d1:05:1c:20:38:a4:dd:19:39:b8:0b:d1:6e: 84:8f:e9:55:df:fd:70:16:c5:f8:5d:66:6a:03:13: 36:7b:ab:e1:7e:30:7e:76:6b:e0:50:2d:cb:ad:59: 9c:db:66:ba:2e:9d:61:50:ad:0f:2f:fe:22:7f:bb: ef:91:f5:70:02:bd:71:2f:6a:93:2f:db:85:fd:3e: 28:cf:39:d1:d4:39:32:e8:c7:a0:3b:10:98:36:44: 69:d3:15:57:bf:59:53:0c:88:01:02:78:9f:69:f6: 1b:c2:20:5a:e0:0e:b2:1f:ed:10:c1:bf:9d:d0:17: 9d:0a:7a:25:8d:84:bc:39:f1:1b:74:11:47:e0:68: 03:21 Exponent: 65537 (0x10001) このように秘密鍵からは公開鍵の中身が得られるが,\n公開鍵から秘密鍵を逆算することはできない ので,\n公開鍵は自由に公開しても問題ない.\n(正しくは逆算は完全に不可能ではないんだけど, やろうとすると非現実的な計算時間が必要になるので誰もやろうとしない)\n公開鍵で暗号化する 秘密鍵と公開鍵が作成できたので,\nまずは試しに 公開鍵を用いてデータを暗号化する.\nここからは2つのディレクトリ(dirA, dirB)をデータの受信側, 送信側に見立てて進める.\n共通鍵暗号方式でデータをやり取りするには, まず受信側(dirA)の公開鍵を送信側(dirB)に渡す.\n# 秘密鍵を作ったのとは別のディレクトリで作業 $ cd .. $ mkdir dirB $ ls dirA dirB # 受信側(dirA)の公開鍵を送信側(dirB)に送る $ cp dirA/public-key.pem dirB/ 次に送信側(dirB)は受信側からもらった公開鍵でデータ(data.txt)を暗号化する.\n公開鍵と平文データで数値計算をごにゃごにゃやった結果の値が暗号データとなる.\n# 送信側で適当な平文データを作る $ cd dirB $ echo abcdefg \u0026gt; data.txt $ cat data.txt abcdefg $ ls data.txt public-key.pem # 公開鍵を使ってデータを暗号化する $ openssl rsautl -encrypt -in data.txt -out encrypted-data.txt -inkey public-key.pem -pubin # 暗号化されたデータが出力される $ ls data.txt encrypted-data.txt public-key.pem # そのまま表示しようとしても意味不明な状態 $ cat encrypted-data.txt �����... 暗号化されたデータは秘密鍵によってのみ復号化できる ので,\nこの暗号データ(encrypted-data.txt)を公開鍵で復号化することはできない.\n# 無理やり復号化を試みる $ openssl rsautl -decrypt -in encrypted-data.txt -out decrypted-data.txt -inkey public-key.pem -pubin A private key is needed for this operation # 秘密鍵じゃないとできないよって怒られる 秘密鍵で復号化する 次に先程暗号化したデータ(encrypted-data.txt)を送信側(dirB)から受信側(dirA)に渡し,\n受信側の秘密鍵で復号化してみる.\n# 送信側(dirB)から暗号データ(encrypted-data.txt)を受信側(dirA)に送る $ cd .. $ cp dirB/encrypted-data.txt dirA/ # 秘密鍵で復号化する $ cd dirA $ openssl rsautl -decrypt -in encrypted-data.txt -out decrypted-data.txt -inkey private-key.pem $ ls decrypted-data.txt encrypted-data.txt private-key.pem public-key.pem # 元のデータが復号化されている $ cat decrypted-data.txt abcdefg 以上の手順で暗号化データを平文データに復号化できた.\n重要なのは\n送信側と受信側の間でやりとりされるデータが受信側の公開鍵と暗号データだけになる こと.\n暗号化されたデータは秘密鍵によってのみ復号化できる ので,\n仮に通信が傍受されても秘密鍵が盗まれない限り情報は守られる.\nこれが公開鍵暗号の考え方.\nパスフレーズをつける これまで確認したように公開鍵による暗号化で情報は守られるが,\n秘密鍵が流出した場合はそうではなくなってしまう.\nこのため, 万が一に備えて秘密鍵自体をさらに暗号化してしまうというのがパスフレーズの考え.\nまずは確認のため, 秘密鍵にパスフレーズを追加してみる.\n# 受信側で作業 $ cd dirA $ ls decrypted-data.txt encrypted-data.txt private-key.pem public-key.pem # 秘密鍵にパスフレーズをつける(暗号化方式はaes256を使用する) $ openssl rsa -in private-key.pem -out private-key-with-pass-phrase.pem -aes256 writing RSA key Enter PEM pass phrase: # 任意のパスフレーズを入力 Verifying - Enter PEM pass phrase: # パスフレーズを再入力 $ ls decrypted-data.txt encrypted-data.txt private-key.pem private-key-with-pass-phrase.pem public-key.pem パスフレーズつきの秘密鍵を使って何かしようとすると,\n必ずパスフレーズの入力を求められる.\nこのため, 万が一第三者に秘密鍵が流出してもパスフレーズがわからない限りは暗号データを復号化できない.\n# 秘密鍵で暗号データを復号化しようとするとパスフレーズを要求される $ openssl rsautl -decrypt -in encrypted-data.txt -out decrypted-data.txt -inkey private-key-with-pass-phrase.pem Enter pass phrase for private-key-with-pass-phrase.pem: # 間違ったパスフレーズを入力 unable to load Private Key # パスフレーズが違うので秘密鍵が使えない ただパスフレーズがあると面倒なパターンもあるので,\nこれを外すこともできる.\n# パスフレーズを外す $ openssl rsa -in private-key-with-pass-phrase.pem -out private-key-without-pass-phrase.pem Enter pass phrase for private-key-with-pass-phrase.pem: # 正しいパスフレーズを入力 writing RSA key # パスフレーズが要求されなくなる $ openssl rsautl -decrypt -in encrypted-data.txt -out decrypted-data.txt -inkey private-key-without-pass-phrase.pem おわり 実際に手を動かして試したおかげでちょっとは理解が深まった気がする.\n公開鍵暗号方式だけではまだセキュリティに問題があるので(暗号化/復号化の計算量や公開鍵の信頼性など),\n時間があればハイブリッド暗号方式とかデジタル証明書についても実際に試してみたい.\nおまけ 参考 秘密鍵と公開鍵の性質 アルゴリズム図鑑 絵で見てわかる26のアルゴリズム めっちゃわかりやすい 秘密鍵と公開鍵を作る https://www.openssl.org/docs/man1.1.0/man1/genrsa.html https://www.openssl.org/docs/man1.1.0/man1/rsa.html https://wiki.openssl.org/index.php/Command_Line_Utilities#Generating_an_RSA_Private_Key 公開鍵で暗号化する, 秘密鍵で復号化する https://www.openssl.org/docs/man1.1.0/man1/rsautl.html パスフレーズをつける https://www.openssl.org/docs/man1.1.0/man1/rsa.html ","date":"2020-05-20T20:18:54+09:00","image":"/post/2020-05-20-public-key-practice/sotochan.jpg","permalink":"/post/2020-05-20-public-key-practice/","title":"OpenSSLで秘密鍵と公開鍵を作る"},{"content":"APIからツイートしたい TwitterのAPIをつかってちょっと遊んでみたくなったのでやってみた.\nやったことのまとめ Twitterの開発者アカウントの利用申請をした APIを利用するためのTwitter appを作成した 公式のコマンドラインツールtwurlを使ってAPI経由でツイートした ついでにanyenvでRubyのセットアップをした つかうもの macOS Mojave 10.14 twurl https://github.com/twitter/twurl version 0.9.5 今回入れる jq https://github.com/stedolan/jq jq-1.6 インストール済み 必須ではないがAPIレスポンスの整形に使用 anyenv https://github.com/anyenv/anyenv anyenv 1.1.1 インストール済み rbenv https://github.com/rbenv/rbenv rbenv 1.1.2-30-gc879cb0 twurlのために今回入れる Twitterのアカウント やったこと 開発者アカウントの利用申請 appの作成 twurlを使ってツイート 開発者アカウントの利用申請 以前遊んだWikipediaのAPIとかは誰でも自由に利用できるけど,\nTwitterのAPIを使うには事前に開発者アカウントの利用申請が必要になるらしいので実際にやってみる.\nブラウザでTwitterにログインした状態で\nhttps://developer.twitter.com/ja/apply-for-access を開く.\n開発者アカウントに申し込む に進む.\n利用目的を聞かれるので, 適当なものを選択する.\n今回は Exploring the API にチェックをつけて進む.\n開発者権限を申請するアカウント(@uzimihsr)の情報を確認する.\n住んでる国とTwitterからのメールで呼ばれたい名前?を設定して次に進む.\nAPIの使いみちを聞かれるので, 200文字以上の英語 で記入する.\n自分の場合は\n\u0026ldquo;趣味でのアプリに使用します. Linux上のコマンドラインツールからツイートの投稿, タイムラインの取得, ツイートの削除などを行う予定です. 今の所はTwitterのデータを分析して何かする予定はありません.\u0026rdquo;\n的な文章を埋めた.\n用途が以下のどれかにあたる場合はさらに詳細な情報を追加しなきゃいけないらしい.\nTwitterのデータ解析を行う場合 ツイート, リツイート, いいね, フォロー, DMの機能を使用する場合 ツイートやTwitterコンテンツの集計情報をTwitter以外の場所で公開する場合 用途が政府機関に関係する場合 自分は2のケースに該当するので,\n\u0026ldquo;curlやjqなどのLinux上のコマンドラインツールを使ってツイートの投稿, タイムラインの取得, ツイートの検索, いいねなどを試そうと思います.\u0026rdquo;\n的な文章を埋めた.\n用途について記入したら次に進む.\nこれまでの入力内容を確認して次に進む.\n最後に利用規約を読んでチェックボックスにチェックを入れて, Submit Application を押す.\n確認用メールを送った旨が表示される.\n申請したアカウントに紐付いたメールアドレスにこんな感じのメールが来るので, Confirm your email を押す.\n利用申請が完了したことを示す画面が開く.\n完了までに時間がかかることもあるみたいだけど, 自分の場合は一瞬だった.\n用途とかで書いた英文をGrammarlyで文法チェックしたおかげかも?\nとりあえず, これでTwitterの開発者アカウントを使えるようになった.\nappの作成 Twitter APIの機能を使うには, appを作成して認証用のconsumer keyとconsumer secretを作成する必要があるらしいのでやってみる.\n先程の利用完了画面か,\nhttps://developer.twitter.com/en/account/get-started\nから Create an app に進む.\n以下の必須項目を埋めて Create を押す.\n必須項目 詳細 App name appの名前(適当につける) Application description appの説明 Website URL appに関連するウェブサイトのURL\n(自分が管理しているものが望ましい) Tell us how this app will be used appの用途\n(Twitterチームがチェックするのでちゃんと書く) 利用規約的なやつの確認モーダルが表示されるのでちゃんと確認して Create する.\napp(uzimihsr-twurl)が作成されて詳細画面が開くので, Keys and tokens に進む.\n表示された API key(consumer key), API secret key(consumer secret)は\n後ほど使用するのでメモしておく.\nこれでAPIを使うためのappの準備は完了.\ntwurlを使ってツイート 今回はcurlっぽくTwitter APIを叩くための公式のコマンドラインツールtwurlを使って実際にツイートしてみる.\nまずはtwurlをgemでインストール\u0026hellip;\nしようとしたら権限エラーでコケた.\nMacに最初から入ってるシステムのRubyだとうまく行かないみたい.\n# twurlのインストール...に失敗 $ gem install twurl Fetching: oauth-0.5.4.gem (100%) ERROR: While executing gem ... (Gem::FilePermissionError) ... 以下の手順でRubyのセットアップを行う.\nanyenvを使ったRubyのセットアップ Ruby全然使ったことないけどanyenvは神なので簡単にセットアップできる.\n公式によると現在の安定版は 2.7.1 なのでこれを入れる.\n# rbenvをインストール $ anyenv install -l | grep rbenv rbenv $ anyenv install rbenv ... Install rbenv succeeded! Please reload your profile (exec $SHELL -l) or open a new session. $ exec $SHELL -l # 動作確認 $ rbenv --version rbenv 1.1.2-30-gc879cb0 # Ruby(2.7.1)をインストール $ rbenv install -l | grep 2.7.1 2.7.1 $ rbenv install 2.7.1 $ rbenv versions * system 2.7.1 # Rubyのバージョンを選択 $ rbenv global 2.7.1 $ exec $SHELL -l $ rbenv versions system * 2.7.1 (set by /Users/uzimihsr/.anyenv/envs/rbenv/version) # 動作確認 $ ruby -v ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-darwin18] $ gem -v 3.1.2 再度twurlをインストール.\n今度は普通にできた.\n# twurlのインストール $ gem install twurl ... Done installing documentation for oauth, twurl after 1 seconds 2 gems installed # 動作確認 $ twurl -v 0.9.5 インストールができたのでさっそく使ってみる.\nまずは手元のtwurlと先ほど作成したapp, そしてappを使用するアカウントを紐付ける.\ntwurl authorizeの引数に先程メモしたappのconsumer keyとconsumer secretを渡すと,\n認証用のURLが表示される.\n# 認証画面を開く $ key=\u0026#39;keykeykeykeykeykeykeykey\u0026#39; # consumer key $ secret=\u0026#39;secretsecretsecretsecret\u0026#39; # consumer secret $ twurl authorize --consumer-key $key --consumer-secret $secret Go to https://api.twitter.com/oauth/authorize?oauth_consumer_key=keykeykeykeykeykeykeykey...\u0026amp;oauth_version=1.0 and paste in the supplied PIN # メッセージに従ってURLをブラウザで開く 指定されたURLをブラウザで開くとアカウントにappを連携させるための確認画面が表示されるので,\nAuthorize app を選択して自分のアカウントとappを連携させる.\n連携に成功すると7桁のPINが表示されるので,\nこれをtwurlのメッセージに従って入力する.\n# 先程のつづき $ twurl authorize --consumer-key $key --consumer-secret $secret Go to https://api.twitter.com/oauth/authorize?oauth_consumer_key=keykeykeykeykeykeykeykey...\u0026amp;oauth_version=1.0 and paste in the supplied PIN 1234567 # PINを入力 Authorization successful # 認証に成功したアカウントの確認 $ twurl accounts uzimihsr keykeykeykeykeykeykeykey (default) これでtwurlと自分のアカウントが紐付いた.\n次にツイート投稿APIを叩いてツイートしてみる.\ntwurlを使って\nhttps://api.twitter.com/1.1/hoge/huga.json\nのようなエンドポイントを叩く場合は,\ntwurl /1.1/hoge/huga.jsonのようにすればいいみたい.\n# APIからツイートしてみる # レスポンスがJSONで返ってくるのでjqでパースする $ twurl -X POST -d \u0026#39;status=そとちゃんかわいい\u0026#39; /1.1/statuses/update.json | jq { \u0026#34;created_at\u0026#34;: \u0026#34;Mon May 18 10:09:54 +0000 2020\u0026#34;, \u0026#34;id\u0026#34;: 1262324524051075000, \u0026#34;id_str\u0026#34;: \u0026#34;1262324524051075074\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;そとちゃんかわいい\u0026#34;, \u0026#34;truncated\u0026#34;: false, \u0026#34;entities\u0026#34;: { \u0026#34;hashtags\u0026#34;: [], \u0026#34;symbols\u0026#34;: [], \u0026#34;user_mentions\u0026#34;: [], \u0026#34;urls\u0026#34;: [] }, \u0026#34;source\u0026#34;: \u0026#34;\u0026lt;a href=\\\u0026#34;https://uzimihsr.github.io/\\\u0026#34; rel=\\\u0026#34;nofollow\\\u0026#34;\u0026gt;uzimihsr-twurl\u0026lt;/a\u0026gt;\u0026#34;, \u0026#34;in_reply_to_status_id\u0026#34;: null, \u0026#34;in_reply_to_status_id_str\u0026#34;: null, \u0026#34;in_reply_to_user_id\u0026#34;: null, \u0026#34;in_reply_to_user_id_str\u0026#34;: null, \u0026#34;in_reply_to_screen_name\u0026#34;: null, \u0026#34;user\u0026#34;: { \u0026#34;id\u0026#34;: 1146420174272073700, \u0026#34;id_str\u0026#34;: \u0026#34;1146420174272073733\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;ずみし\u0026#34;, \u0026#34;screen_name\u0026#34;: \u0026#34;uzimihsr\u0026#34;, \u0026#34;location\u0026#34;: \u0026#34;日本 東京\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;超絶かわいい元保護猫そとちゃんのしもべです\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;https://t.co/mM0q9F0x2t\u0026#34;, \u0026#34;entities\u0026#34;: { \u0026#34;url\u0026#34;: { \u0026#34;urls\u0026#34;: [ { \u0026#34;url\u0026#34;: \u0026#34;https://t.co/mM0q9F0x2t\u0026#34;, \u0026#34;expanded_url\u0026#34;: \u0026#34;https://instagram.com/uzimihsr\u0026#34;, \u0026#34;display_url\u0026#34;: \u0026#34;instagram.com/uzimihsr\u0026#34;, \u0026#34;indices\u0026#34;: [ 0, 23 ] } ] }, \u0026#34;description\u0026#34;: { \u0026#34;urls\u0026#34;: [] } }, \u0026#34;protected\u0026#34;: false, \u0026#34;followers_count\u0026#34;: 35, \u0026#34;friends_count\u0026#34;: 52, \u0026#34;listed_count\u0026#34;: 0, \u0026#34;created_at\u0026#34;: \u0026#34;Wed Jul 03 14:07:23 +0000 2019\u0026#34;, \u0026#34;favourites_count\u0026#34;: 32, \u0026#34;utc_offset\u0026#34;: null, \u0026#34;time_zone\u0026#34;: null, \u0026#34;geo_enabled\u0026#34;: true, \u0026#34;verified\u0026#34;: false, \u0026#34;statuses_count\u0026#34;: 227, \u0026#34;lang\u0026#34;: null, \u0026#34;contributors_enabled\u0026#34;: false, \u0026#34;is_translator\u0026#34;: false, \u0026#34;is_translation_enabled\u0026#34;: false, \u0026#34;profile_background_color\u0026#34;: \u0026#34;F5F8FA\u0026#34;, \u0026#34;profile_background_image_url\u0026#34;: null, \u0026#34;profile_background_image_url_https\u0026#34;: null, \u0026#34;profile_background_tile\u0026#34;: false, \u0026#34;profile_image_url\u0026#34;: \u0026#34;http://pbs.twimg.com/profile_images/1220189466368692224/VkTo35n4_normal.jpg\u0026#34;, \u0026#34;profile_image_url_https\u0026#34;: \u0026#34;https://pbs.twimg.com/profile_images/1220189466368692224/VkTo35n4_normal.jpg\u0026#34;, \u0026#34;profile_banner_url\u0026#34;: \u0026#34;https://pbs.twimg.com/profile_banners/1146420174272073733/1584881012\u0026#34;, \u0026#34;profile_link_color\u0026#34;: \u0026#34;1DA1F2\u0026#34;, \u0026#34;profile_sidebar_border_color\u0026#34;: \u0026#34;C0DEED\u0026#34;, \u0026#34;profile_sidebar_fill_color\u0026#34;: \u0026#34;DDEEF6\u0026#34;, \u0026#34;profile_text_color\u0026#34;: \u0026#34;333333\u0026#34;, \u0026#34;profile_use_background_image\u0026#34;: true, \u0026#34;has_extended_profile\u0026#34;: false, \u0026#34;default_profile\u0026#34;: true, \u0026#34;default_profile_image\u0026#34;: false, \u0026#34;following\u0026#34;: false, \u0026#34;follow_request_sent\u0026#34;: false, \u0026#34;notifications\u0026#34;: false, \u0026#34;translator_type\u0026#34;: \u0026#34;none\u0026#34; }, \u0026#34;geo\u0026#34;: null, \u0026#34;coordinates\u0026#34;: null, \u0026#34;place\u0026#34;: null, \u0026#34;contributors\u0026#34;: null, \u0026#34;is_quote_status\u0026#34;: false, \u0026#34;retweet_count\u0026#34;: 0, \u0026#34;favorite_count\u0026#34;: 0, \u0026#34;favorited\u0026#34;: false, \u0026#34;retweeted\u0026#34;: false, \u0026#34;lang\u0026#34;: \u0026#34;ja\u0026#34; } 実際に投稿されたツイートがこちら.\n連携したapp(uzimihsr-twurl)から投稿されている.\nそとちゃんかわいい\n\u0026mdash; ずみし (@uzimihsr) May 18, 2020 https://twitter.com/uzimihsr/status/1262324524051075074\nやったぜ.\nコマンドラインからTwitter APIを使ってツイートすることができた.\nおわり 以上の手順で開発者アカウントの利用申請からTwitter APIを用いたツイートまでをやってみた.\nTwitter大好き芸人なのでtwurlのコード読んだりAPIリファレンス読んだりしていろいろ遊んでみたい.\nおまけ 参考 開発者アカウントの利用申請 https://help.twitter.com/ja/rules-and-policies/twitter-api https://developer.twitter.com/ja/apply-for-access appの作成 https://developer.twitter.com/ja/docs/basics/apps/overview twurlを使ってツイート https://developer.twitter.com/en/docs/tutorials/using-twurl https://github.com/twitter/twurl https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update ","date":"2020-05-18T19:56:42+09:00","image":"/post/2020-05-18-twitter-api-application/sotochan.jpg","permalink":"/post/2020-05-18-twitter-api-application/","title":"Twitter APIを使ってコマンドラインからツイートする"},{"content":"ServiceとかIngressとか Kubernetes完全ガイドの続き.\n次はServiceとかの話.\n読んだもの Kubernetes完全ガイド 6章(Discovery \u0026amp; LBリソース) 重要そうなところとかよく使いそうなところだけまとめる.\n読んだことのまとめ Service ClusterIP Service ExternalIP Service NodePort Service LoadBalancer Service Headless Service ExternalName Service None-Selector Service Ingress Service Podへのトラフィックの負荷分散とサービスディスカバリを行うリソース.\nよく使いそうな設定項目 説明 .spec.type Serviceの種類を指定する .spec.ports[] トラフィックを受け付けるポートと\n転送先コンテナのポートに関する設定 .spec.selector Serviceの対象となるPodのラベル Serviceは指定したラベルを持つPodへのトラフィックを振り分けたり,\nService名から対象となるPodを探し出したりする.\nPodは作り直すたびにIPが変わってしまうので,\nラベルで管理するServiceを使うととても便利.\nClusterIP Service 一番簡単なService. マニフェストの.spec.typeはClusterIP.\nよく使いそうな設定項目 説明 .spec.ports[].port トラフィックを受け付けるポート .spec.ports[].targetPort 転送先Podのポート(コンテナ) クラスタ内のみで有効な仮想IPを作成し, トラフィックをコンテナに振り分ける.\n基本的にはクラスタ内でのロードバランサ(負荷分散装置)として使用する.\n# Deploymentが存在し, Podのラベル(app=sample-app)を指定するClusterIP Serviceが作成済の状態 $ kubectl get deployments NAME READY UP-TO-DATE AVAILABLE AGE sample-deployment 3/3 3 3 80s $ kubectl get pods -l app=sample-app -o custom-columns=\u0026#34;NAME:{metadata.name},IP:{status.podIP}\u0026#34; NAME IP sample-deployment-6c5948bf66-c5j2l 10.4.2.40 sample-deployment-6c5948bf66-fwdzj 10.4.1.14 sample-deployment-6c5948bf66-xkn4v 10.4.2.41 $ kubectl get service sample-clusterip NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE sample-clusterip ClusterIP 10.7.246.109 \u0026lt;none\u0026gt; 8080/TCP 13s # Service(10.7.246.109)の8080番宛のトラフィックを各Podの80番ポートに転送する設定 $ kubectl describe service sample-clusterip Name: sample-clusterip Namespace: default Labels: \u0026lt;none\u0026gt; Annotations: kubectl.kubernetes.io/last-applied-configuration: ... Selector: app=sample-app Type: ClusterIP IP: 10.7.246.109 Port: http-port 8080/TCP TargetPort: 80/TCP Endpoints: 10.4.1.14:80,10.4.2.40:80,10.4.2.41:80 Session Affinity: None Events: \u0026lt;none\u0026gt; # ロードバランシングの確認のため, 各Podのnginxで表示するページにPod名を記述 $ for PODNAME in $(kubectl get pods -l app=sample-app -o jsonpath=\u0026#39;{.items[*].metadata.name}\u0026#39;); do kubectl exec -it ${PODNAME} -- cp /etc/hostname /usr/share/nginx/html/index.html; done # 使い捨てのPodからCurlして確認 # 何回か繰り返すと各Pod名が表示され, 負荷分散されていることがわかる $ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -s http://10.7.246.109:8080 sample-deployment-6c5948bf66-c5j2l pod \u0026#34;testpod\u0026#34; deleted $ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -s http://10.7.246.109:8080 sample-deployment-6c5948bf66-fwdzj pod \u0026#34;testpod\u0026#34; deleted $ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -s http://10.7.246.109:8080 sample-deployment-6c5948bf66-xkn4v pod \u0026#34;testpod\u0026#34; deleted # Serviceが存在する状態でPodを再作成してIPが変わってもラベルが同じなら負荷分散の設定が効く $ kubectl delete deployment sample-deployment deployment.extensions \u0026#34;sample-deployment\u0026#34; deleted $ kubectl apply -f sample-deployment.yaml deployment.apps/sample-deployment created $ kubectl get pods -l app=sample-app -o custom-columns=\u0026#34;NAME:{metadata.name},IP:{status.podIP}\u0026#34; NAME IP sample-deployment-6c5948bf66-g4dpz 10.4.2.58 sample-deployment-6c5948bf66-h9qhx 10.4.2.59 sample-deployment-6c5948bf66-vvtl8 10.4.1.15 $ kubectl describe service sample-clusterip | grep Endpoints Endpoints: 10.4.1.15:80,10.4.2.58:80,10.4.2.59:80 # Podの環境変数からServiceの情報を取得できる(サービスディスカバリ) $ kubectl exec -it sample-deployment-6c5948bf66-g4dpz printenv | grep SAMPLE_CLUSTERIP SAMPLE_CLUSTERIP_PORT=tcp://10.7.246.109:8080 SAMPLE_CLUSTERIP_PORT_8080_TCP_PORT=8080 SAMPLE_CLUSTERIP_SERVICE_HOST=10.7.246.109 SAMPLE_CLUSTERIP_SERVICE_PORT=8080 SAMPLE_CLUSTERIP_PORT_8080_TCP_ADDR=10.7.246.109 SAMPLE_CLUSTERIP_SERVICE_PORT_HTTP_PORT=8080 SAMPLE_CLUSTERIP_PORT_8080_TCP=tcp://10.7.246.109:8080 SAMPLE_CLUSTERIP_PORT_8080_TCP_PROTO=tcp # Service名からIPを正引きできる # このときのFQDNは\u0026lt;Service名\u0026gt;.\u0026lt;namespace\u0026gt;.svc.cluster.localを指定する $ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- dig sample-clusterip.default.svc.cluster.local ... ;; QUESTION SECTION: ;sample-clusterip.default.svc.cluster.local. IN A ;; ANSWER SECTION: sample-clusterip.default.svc.cluster.local. 30 IN A 10.7.246.109 ... pod \u0026#34;testpod\u0026#34; deleted # 逆引きも可能 $ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- dig -x 10.7.246.109 ... ;; QUESTION SECTION: ;109.246.7.10.in-addr.arpa.\tIN\tPTR ;; ANSWER SECTION: 109.246.7.10.in-addr.arpa. 30\tIN\tPTR\tsample-clusterip.default.svc.cluster.local. ... pod \u0026#34;testpod\u0026#34; deleted sample-deployment.yaml\nsample-clusterip.yaml\nExternalIP Service ClusterIP Serviceの機能に加えてクラスタ外からのトラフィックもコンテナに振り分けるService.\nマニフェストの.spec.typeはClusterIPであることに注意.\nよく使いそうな設定項目 説明 .spec.externalIPs[] トラフィックを受け付けるNodeのIP ExternalIP ServiceではクラスタのNodeのIPとポートを指定し,\nそこへ来たトラフィックをPodの任意のポート(コンテナ)に振り分ける.\nNodeのIPを指定するため,\nNodeへ疎通できるネットワークからであればクラスタ外からでもクラスタ内のコンテナに疎通できる.\nクラスタ内からのトラフィックについてはClusterIP Serviceと同様に仮想IPで受けてPodに振り分ける.\n# Podの状態 $ kubectl get pods -o custom-columns=\u0026#34;NAME:{.metadata.name},PodIP:{.status.podIP}\u0026#34; NAME PodIP sample-deployment-6c5948bf66-9j5jh 10.4.2.3 sample-deployment-6c5948bf66-9md5t 10.4.2.5 sample-deployment-6c5948bf66-ghbrx 10.4.2.4 # Nodeの状態 $ kubectl get nodes -o custom-columns=\u0026#34;NAME:{metadata.name},IP:{status.addresses[].address}\u0026#34; NAME IP gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-0dgf 10.138.0.9 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-n2mf 10.138.0.10 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-wk64 10.138.0.8 # ExternalIP Service(sample-externalip)が存在する状態 $ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.7.240.1 \u0026lt;none\u0026gt; 443/TCP 93d sample-externalip ClusterIP 10.7.241.169 10.138.0.9,10.138.0.10 8080/TCP 50s # Nodeのうち2つ(10.138.0.9,10.138.0.10)の8080番へのトラフィックを各Podの80番に転送する設定 # クラスタ内からのトラフィックは仮想IP(10.7.241.169:8080)で受ける $ kubectl describe service sample-externalip Name: sample-externalip Namespace: default Labels: \u0026lt;none\u0026gt; Annotations: kubectl.kubernetes.io/last-applied-configuration: ... Selector: app=sample-app Type: ClusterIP IP: 10.7.241.169 External IPs: 10.138.0.9,10.138.0.10 Port: http-port 8080/TCP TargetPort: 80/TCP Endpoints: 10.4.2.3:80,10.4.2.4:80,10.4.2.5:80 Session Affinity: None Events: \u0026lt;none\u0026gt; # 今回はクラスタがGKEで提供されているため, 以下のcurlは同じネットワークのインスタンスから実行 # ExternalIPで指定されているNodeのIPとポートを叩くとコンテナへ転送される $ curl http://10.138.0.9:8080 sample-deployment-6c5948bf66-9md5t $ curl http://10.138.0.9:8080 sample-deployment-6c5948bf66-9j5jh $ curl http://10.138.0.9:8080 sample-deployment-6c5948bf66-ghbrx $ curl http://10.138.0.10:8080 sample-deployment-6c5948bf66-9md5t $ curl http://10.138.0.10:8080 sample-deployment-6c5948bf66-9j5jh $ curl http://10.138.0.10:8080 sample-deployment-6c5948bf66-ghbrx # ExternalIPで指定していないNodeを叩いてもコンテナへ疎通できない $ curl http://10.138.0.8:8080 curl: (7) Failed to connect to 10.138.0.8 port 8080: Connection refused sample-externalip.yaml(.spec.externalIPs[]はNodeに合わせて書き換える)\nNodePort Service ExternalIP Serviceと同様にクラスタ外からのトラフィックを受けるService.\nマニフェストの.spec.typeはNodePort.\nよく使いそうな設定項目 説明 .spec.ports[].nodePort クラスタ外からのトラフィックを受けるポートを指定\n(指定しない場合は自動で決定)\nすべてのNodeで使用可能なポートを指定する必要がある ExternalIP Serviceとは異なり,\nクラスタのすべてのNodeの指定したポートへのトラフィックをクラスタ内のPodに転送する.\n# Podの状態 $ kubectl get pods -o custom-columns=\u0026#34;NAME:{.metadata.name},PodIP:{.status.podIP}\u0026#34; NAME PodIP sample-deployment-6cd85bd5f-57nsj 10.0.0.6 sample-deployment-6cd85bd5f-5rgtk 10.0.2.3 sample-deployment-6cd85bd5f-bwq9t 10.0.1.3 # NodePort Service(sample-nodeport)が存在する状態 $ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.4.0.1 \u0026lt;none\u0026gt; 443/TCP 12d sample-nodeport NodePort 10.4.13.251 \u0026lt;none\u0026gt; 8080:30080/TCP 7s # 各Nodeの30080番へのトラフィックを各Podの80番へ転送する設定 # クラスタ内からのトラフィックは仮想IP(10.4.13.251)の8080番で受ける $ kubectl describe service sample-nodeport Name: sample-nodeport Namespace: default Labels: \u0026lt;none\u0026gt; Annotations: kubectl.kubernetes.io/last-applied-configuration: ... Selector: app=sample-app Type: NodePort IP: 10.4.13.251 Port: http-port 8080/TCP TargetPort: 80/TCP NodePort: http-port 30080/TCP Endpoints: 10.0.0.6:80,10.0.1.3:80,10.0.2.3:80 Session Affinity: None External Traffic Policy: Cluster Events: \u0026lt;none\u0026gt; # Nodeの外部IPを確認 $ kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME gke-k8s-01-pool-1-2c84f666-8jwm Ready \u0026lt;none\u0026gt; 13h v1.14.10-gke.17 10.128.15.192 www.www.www.www Container-Optimized OS from Google 4.14.138+ docker://18.9.7 gke-k8s-01-pool-2-641104a4-7r06 Ready \u0026lt;none\u0026gt; 14h v1.14.10-gke.17 10.128.0.60 xxx.xxx.xxx.xxx Container-Optimized OS from Google 4.14.138+ docker://18.9.7 gke-k8s-01-pool-2-641104a4-gbrv Ready \u0026lt;none\u0026gt; 20h v1.14.10-gke.17 10.128.0.56 yyy.yyy.yyy.yyy Container-Optimized OS from Google 4.14.138+ docker://18.9.7 gke-k8s-01-pool-2-641104a4-zgjm Ready \u0026lt;none\u0026gt; 23h v1.14.10-gke.17 10.128.0.55 zzz.zzz.zzz.zzz Container-Optimized OS from Google 4.14.138+ docker://18.9.7 # GKEなのでGCPのインスタンス一覧からでも確認できる $ gcloud compute instances list NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS gke-k8s-01-pool-1-2c84f666-8jwm us-central1-a f1-micro 10.128.15.192 www.www.www.www RUNNING gke-k8s-01-pool-2-641104a4-7r06 us-central1-a n1-standard-1 true 10.128.0.60 xxx.xxx.xxx.xxx RUNNING gke-k8s-01-pool-2-641104a4-gbrv us-central1-a n1-standard-1 true 10.128.0.56 yyy.yyy.yyy.yyy RUNNING gke-k8s-01-pool-2-641104a4-zgjm us-central1-a n1-standard-1 true 10.128.0.55 zzz.zzz.zzz.zzz RUNNING # Nodeの外部IPの指定されたポートを叩くとちゃんと転送される $ curl http://www.www.www.www:30080 sample-deployment-6cd85bd5f-bwq9t $ curl http://xxx.xxx.xxx.xxx:30080 sample-deployment-6cd85bd5f-5rgtk $ curl http://yyy.yyy.yyy.yyy:30080 sample-deployment-6cd85bd5f-57nsj $ curl http://zzz.zzz.zzz.zzz:30080 sample-deployment-6cd85bd5f-57nsj sample-nodeport.yaml\nLoadBalancer Service クラスタ外部のロードバランサーを利用してクラスタ外からのトラフィックを受けるService.\nマニフェストの.spec.typeはLoadBalancer.\nよく使いそうな設定項目 説明 .spec.ports[].port 外部ロードバランサーでトラフィックを受けるポートと\nクラスタ内からのトラフィックを受けるポートを指定 .spec.ports[].nodePort NodePort Serviceと同じ .spec.loadBalancerIP 利用可能な場合のみ\n外部ロードバランサーの静的IPを指定 .spec.loadBalancerSourceRanges 外部ロードバランサーのファイアウォールで\n許可する通信元の設定\n(未指定の場合は0.0.0.0/0になる) LoadBalancer Serviceクラスタ外にあるLoadBalancer宛のトラフィックをクラスタのNodeに対して振り分け,\nNodeに届いたトラフィックはNodePort Serviceの仕組みでPodに転送される.\n# Podの状態 $ kubectl get pods -o custom-columns=\u0026#34;NAME:{.metadata.name},PodIP:{.status.podIP}\u0026#34; NAME PodIP sample-deployment-6cd85bd5f-57nsj 10.0.0.6 sample-deployment-6cd85bd5f-5rgtk 10.0.2.3 sample-deployment-6cd85bd5f-bwq9t 10.0.1.3 # LoadBalancer Service(sample-lb)が存在する状態 $ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.4.0.1 \u0026lt;none\u0026gt; 443/TCP 12d sample-lb LoadBalancer 10.4.13.107 34.71.248.88 8080:30082/TCP 5m43s # 外部ロードバランサーの8080番に届いたトラフィックを各Nodeの30082番に振り分ける設定 # NodePort Serviceを内包しているのでNodeの30082番に届いたトラフィックは各Podの80番に振り分けられる # クラスタ内からのトラフィックは仮想IP(10.4.13.107)の8080番で受ける $ kubectl describe service sample-lb Name: sample-lb Namespace: default Labels: \u0026lt;none\u0026gt; Annotations: kubectl.kubernetes.io/last-applied-configuration: ... Selector: app=sample-app Type: LoadBalancer IP: 10.4.13.107 LoadBalancer Ingress: 34.71.248.88 Port: http-port 8080/TCP TargetPort: 80/TCP NodePort: http-port 30082/TCP Endpoints: 10.0.0.6:80,10.0.1.3:80,10.0.2.3:80 Session Affinity: None External Traffic Policy: Cluster Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal EnsuringLoadBalancer 6m46s service-controller Ensuring load balancer Normal EnsuredLoadBalancer 5m47s service-controller Ensured load balancer # GKEの場合はLoadBalancer Serviceの作成時に自動でロードバランサーが払い出される $ gcloud compute forwarding-rules list NAME REGION IP_ADDRESS IP_PROTOCOL TARGET a0c33af4a736211ea911a42010a80001 us-central1 34.71.248.88 TCP us-central1/targetPools/a0c33af4a736211ea911a42010a80001 # 外部ロードバランサーの8080番を叩くとPodに転送される $ curl http://www.www.www.www:8080 sample-deployment-6cd85bd5f-bwq9t $ curl http://www.www.www.www:8080 sample-deployment-6cd85bd5f-5rgtk $ curl http://www.www.www.www:8080 sample-deployment-6cd85bd5f-57nsj sample-lb.yaml\nHeadless Service 仮想IPではなくDNS Round Robinでエンドポイントを提供するService.\nマニフェストの.spec.typeはClusterIP.\nよく使いそうな設定項目 説明 .spec.clusterIP Noneのみ指定可能 Headless Serviceの名前解決時には\u0026lt;serviceName\u0026gt;.\u0026lt;namespace\u0026gt;.svc.cluster.localで正引きできる.\nPodがStatefulSetの場合のみ, \u0026lt;podName\u0026gt;.\u0026lt;serviceName\u0026gt;.\u0026lt;namespace\u0026gt;.svc.cluster.localでPod単位で正引きできる.\n# Podが存在する状態 $ kubectl get pods -o custom-columns=\u0026#34;NAME:{.metadata.name},PodIP:{.status.podIP}\u0026#34; NAME PodIP sample-statefulset-headless-0 10.0.1.2 sample-statefulset-headless-1 10.0.1.3 sample-statefulset-headless-2 10.0.1.4 # 動作確認用にnginxで表示するHTMLにPod名を記述 $ for PODNAME in $(kubectl get pods -l app=sample-app -o jsonpath=\u0026#39;{.items[*].metadata.name}\u0026#39;); do kubectl exec -it ${PODNAME} -- cp /etc/hostname /usr/share/nginx/html/index.html; done # Headless Service(sample-headless)が存在する状態 $ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.4.0.1 \u0026lt;none\u0026gt; 443/TCP 12d sample-headless ClusterIP None \u0026lt;none\u0026gt; 80/TCP 19s # sample-headlessの80番へのトラフィックを各Podの80番に転送する設定 $ kubectl describe service sample-headless Name: sample-headless Namespace: default Labels: \u0026lt;none\u0026gt; Annotations: kubectl.kubernetes.io/last-applied-configuration: ... Selector: app=sample-app Type: ClusterIP IP: None Port: http-port 80/TCP TargetPort: 80/TCP Endpoints: 10.0.1.2:80,10.0.1.3:80,10.0.1.4:80 Session Affinity: None Events: \u0026lt;none\u0026gt; # 使い捨てのPodから名前解決を行うとPodのIPが返ってくる $ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- dig sample-headless.default.svc.cluster.local +short 10.0.1.4 10.0.1.3 10.0.1.2 pod \u0026#34;testpod\u0026#34; deleted # 使い捨てのPodからsample-headlessを叩くとRound Robin方式で各Podへ振り分けられる $ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -sSL sample-headless.default.svc.cluster.local sample-statefulset-headless-0 pod \u0026#34;testpod\u0026#34; deleted $ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -sSL sample-headless.default.svc.cluster.local sample-statefulset-headless-1 pod \u0026#34;testpod\u0026#34; deleted $ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -sSL sample-headless.default.svc.cluster.local sample-statefulset-headless-2 pod \u0026#34;testpod\u0026#34; deleted # PodがStatefulSetで管理されている場合のみPod名でも名前解決できる $ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- dig sample-statefulset-headless-0.sample-headless.default.svc.cluster.local +short 10.0.1.2 pod \u0026#34;testpod\u0026#34; deleted $ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- dig sample-statefulset-headless-1.sample-headless.default.svc.cluster.local +short 10.0.1.3 pod \u0026#34;testpod\u0026#34; deleted $ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- dig sample-statefulset-headless-2.sample-headless.default.svc.cluster.local +short 10.0.1.4 pod \u0026#34;testpod\u0026#34; deleted sample-statefulset-headless.yaml\nsample-headless.yaml\nExternalName Service 名前解決に対して外部ドメインのCNAMEを返すService.\nマニフェストの.spec.typeはExternalName.\nよく使いそうな設定項目 説明 .spec.externalName 外部ドメインのCNAMEを指定できる クラスタ内から外部のサービスに対してアクセスする際はExternalName Serviceに対して名前解決を行い得られたCNAMEを利用すればいいので,\nPod内に外部サービスのアクセス先情報を持たせる必要がなくなり,\nアクセス先を変える場合はExternalName Serviceの設定変更のみで行うことができる(疎結合).\n# ExternalName Service(sample-externalname)が存在する状態 $ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.4.0.1 \u0026lt;none\u0026gt; 443/TCP 13d sample-externalname ExternalName \u0026lt;none\u0026gt; example.com \u0026lt;none\u0026gt; 5s # example.comをCNAMEとして返す設定 $ kubectl describe service sample-externalname Name: sample-externalname Namespace: default Labels: \u0026lt;none\u0026gt; Annotations: kubectl.kubernetes.io/last-applied-configuration: ... Selector: \u0026lt;none\u0026gt; Type: ExternalName IP: External Name: example.com Session Affinity: None Events: \u0026lt;none\u0026gt; # 使い捨てのPodから名前解決するとCNAMEが返ってくる $ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- dig sample-externalname.default.svc.cluster.local CNAME +short example.com. pod \u0026#34;testpod\u0026#34; deleted # クラスタ内からそのままServiceを叩いてもエラーになる $ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -I -sSL sample-externalname.default.svc.cluster.local HTTP/1.1 404 Not Found Content-Type: text/html Date: Wed, 01 Apr 2020 14:54:34 GMT Server: ECS (ord/4CF4) Content-Length: 345 pod \u0026#34;testpod\u0026#34; deleted # ホストヘッダを付与してServiceを叩くとexample.comに接続できる(本来の用途ではないはず...) $ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -I -sSL sample-externalname.default.svc.cluster.local -H \u0026#39;Host:example.com\u0026#39; HTTP/1.1 200 OK Accept-Ranges: bytes Age: 522515 Cache-Control: max-age=604800 Content-Type: text/html; charset=UTF-8 Date: Wed, 01 Apr 2020 14:54:39 GMT Etag: \u0026#34;3147526947+ident\u0026#34; Expires: Wed, 08 Apr 2020 14:54:39 GMT Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT Server: ECS (ord/572A) X-Cache: HIT Content-Length: 1256 pod \u0026#34;testpod\u0026#34; deleted sample-externalname.yaml(.spec.externalNameは好きな外部のドメインに書き換える)\nNone-Selector Service ExternalName Serviceと異なり, 名前解決に対してCNAMEを返すのではなく指定したIPに対してロードバランシングするService.\nマニフェストの.spec.typeはClusterIP.\nよく使いそうな設定項目 説明 .metadata.name(Endpoint) Serviceと同じ名前を指定 .subnets[].addresses[].ip(Endpoint) ロードバランシングしたいIPを指定 .spec.selectorがなく.spec.typeがClusterIPのServiceなので,\n仮想IP(ClusterIP)を叩かれたときの転送先の設定を同じ名前のEndpointsに定義する必要がある.\n(実は他のServiceは生成時に自動で対応するEndpointsを作っている.)\nちょっとむずかしいけど, 要はクラスタ内からの通信に対して任意のロードバランサーが作れる仕組み.\n# None-Selector Service(sample-none-selector)が存在する状態 $ kubectl get services sample-none-selector NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE sample-none-selector ClusterIP 10.4.2.165 \u0026lt;none\u0026gt; 8080/TCP 12s # クラスタ内から仮想IP(10.4.2.165)に対して名前解決した場合17.178.96.59(apple.com)か172.217.175.110(google.com)に分ける設定 # あくまでわかりやすくするための例. 本当はちゃんと同じ外部サービスのIPを複数指定するべき. $ kubectl describe service sample-none-selector Name: sample-none-selector Namespace: default Labels: \u0026lt;none\u0026gt; Annotations: kubectl.kubernetes.io/last-applied-configuration: ... Selector: \u0026lt;none\u0026gt; Type: ClusterIP IP: 10.4.2.165 Port: \u0026lt;unset\u0026gt; 8080/TCP TargetPort: 80/TCP Endpoints: 17.178.96.59:80,172.217.175.110:80 Session Affinity: None Events: \u0026lt;none\u0026gt; # 手動で作成したEndpoints $ kubectl get endpoints sample-none-selector NAME ENDPOINTS AGE sample-none-selector 17.178.96.59:80,172.217.175.110:80 78s # None-Selector Serviceは同じ名前のEndpointsに設定されたIPを参照している $ kubectl describe endpoints sample-none-selector Name: sample-none-selector Namespace: default Labels: \u0026lt;none\u0026gt; Annotations: kubectl.kubernetes.io/last-applied-configuration: ... Subsets: Addresses: 17.178.96.59,172.217.175.110 NotReadyAddresses: \u0026lt;none\u0026gt; Ports: Name Port Protocol ---- ---- -------- \u0026lt;unset\u0026gt; 80 TCP Events: \u0026lt;none\u0026gt; # 使い捨てのPodから仮想IPを叩くと指定した外部IPに転送される $ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -I -s 10.4.2.165:8080 HTTP/1.1 301 Moved Permanently Location: http://www.google.com:8080/ Content-Type: text/html; charset=UTF-8 Date: Thu, 02 Apr 2020 14:38:32 GMT Expires: Sat, 02 May 2020 14:38:32 GMT Cache-Control: public, max-age=2592000 Server: gws Content-Length: 224 X-XSS-Protection: 0 X-Frame-Options: SAMEORIGIN pod \u0026#34;testpod\u0026#34; deleted $ kubectl run --image=centos:6 --restart=Never --rm -i testpod -- curl -I -s 10.4.2.165:8080 HTTP/1.1 301 MOVED PERMANENTLY Server: Apache Date: Thu, 02 Apr 2020 14:38:37 GMT Location: https://www.apple.com/ Content-type: text/html Connection: close pod \u0026#34;testpod\u0026#34; deleted sample-none-selector.yaml(Endpointsの.subnets[].addresses[].ipは有効な外部のドメインに書き換える)\nIngress Serviceとは異なり, L7レベルのロードバランシングを行うリソース.\nIngress自体の実装方法にもいろいろ種類はあるけど,\n基本的には自身へのトラフィックをパスに応じたServiceに転送する.\nnginxのリバースプロキシみたいな感じ.\nよく使いそうな設定項目 説明 .spec.rules[].host 仮想ホスト名 .spec.rules[].http.paths[].path トラフィックを振り分けるためのパスを設定 .spec.rules[].http.paths[].backend.serviceName ~.pathへのトラフィックを\n転送する先のService名を指定 .spec.rules[].http.paths[].backend.servicePort ~.serviceNameのServiceのポート番号を指定 .spec.backend.serviceName パスを指定されなかった場合にトラフィックを転送するService名を指定 .spec.backend.servicePort パスを指定されなかった場合にトラフィックを転送するServiceのポート番号を指定 GKEの場合はクラスタ外のロードバランサーを利用し,\nそのロードバランサー宛のトラフィックをServiceに転送する.\n# Podとそれに紐づくServiceが存在する状態 # 各Serviceは自身の8888番へのアクセスを同じsuffixがついたPodに転送する設定 $ kubectl get all NAME READY STATUS RESTARTS AGE pod/sample-ingress-apps-1 1/1 Running 0 62s pod/sample-ingress-apps-2 1/1 Running 0 61s pod/sample-ingress-default 1/1 Running 0 60s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/sample-ingress-default NodePort 10.4.2.125 \u0026lt;none\u0026gt; 8888:32343/TCP 62s service/sample-ingress-svc-1 NodePort 10.4.4.202 \u0026lt;none\u0026gt; 8888:30755/TCP 63s service/sample-ingress-svc-2 NodePort 10.4.0.222 \u0026lt;none\u0026gt; 8888:31640/TCP 62s # 各Podへのリクエストに対してPod名を返す設定 $ kubectl exec -it sample-ingress-apps-1 -- mkdir /usr/share/nginx/html/path1 $ kubectl exec -it sample-ingress-apps-1 -- /bin/sh -c \u0026#39;hostname \u0026gt; /usr/share/nginx/html/path1/index.html\u0026#39; $ kubectl exec -it sample-ingress-apps-2 -- mkdir /usr/share/nginx/html/path2 $ kubectl exec -it sample-ingress-apps-2 -- /bin/sh -c \u0026#39;hostname \u0026gt; /usr/share/nginx/html/path2/index.html\u0026#39; $ kubectl exec -it sample-ingress-default -- /bin/sh -c \u0026#39;hostname \u0026gt; /usr/share/nginx/html/index.html\u0026#39; # Ingress(sample-ingress)はパスに応じて違うBackend(Service)にトラフィックを分ける設定 $ kubectl get ingresses NAME HOSTS ADDRESS PORTS AGE sample-ingress sample.example.com 35.190.31.149 80, 443 2d1h $ kubectl describe ingress sample-ingress Name: sample-ingress Namespace: default Address: 35.190.31.149 Default backend: sample-ingress-default:8888 (\u0026lt;none\u0026gt;) TLS: tls-sample terminates sample.example.com Rules: Host Path Backends ---- ---- -------- sample.example.com /path1/* sample-ingress-svc-1:8888 (\u0026lt;none\u0026gt;) /path2/* sample-ingress-svc-2:8888 (\u0026lt;none\u0026gt;) Annotations: ... Events: ... # Ingress宛にリクエストするとパスに応じて異なるServiceからそれぞれのPodにルーティングされる $ curl http://35.190.31.149/index.html -H \u0026#34;Host: sample.example.com\u0026#34; sample-ingress-default $ curl http://35.190.31.149/path1/index.html -H \u0026#34;Host: sample.example.com\u0026#34; sample-ingress-apps-1 $ curl http://35.190.31.149/path2/index.html -H \u0026#34;Host: sample.example.com\u0026#34; sample-ingress-apps-2 sample-ingress-apps.yaml\nsample-ingress.yaml\nおまけ キメ顔ねこ\n","date":"2020-05-10T10:17:07+09:00","image":"/post/2020-05-10-kubernetes-guide-chap6/sotochan.jpg","permalink":"/post/2020-05-10-kubernetes-guide-chap6/","title":"Kubernetes完全に理解したい 6章"},{"content":"ログインが面倒 Grafanaのダッシュボードを他の人に見せるときにいちいちパスワードを入力してログインしてもらうのが面倒だと思った.\nやったことのまとめ Snapshotを使ってダッシュボードを共有した ダッシュボードをログインなしで閲覧できるよう設定した ログインなしで見られるダッシュボードをOrganizationで分けた つかうもの Raspberry Pi 3 Model B+ OSはRaspbian(10.0) 監視サーバとして使用 Grafana v6.5.2 インストール済み やったこと Snapshotを使う anonymous accessを有効にする Organizationを分ける Snapshotを使う 初期設定のGrafanaを開くとIDとパスワードでのログインを求められる.\n自分が管理者であれば普通にログインするだけなんだけど,\n他の人にダッシュボードを共有するときにいちいち新規ユーザーを作ってパスワードを発行するのはめんどくさい.\nこんなときにSnapshotを使うとダッシュボードの状態を保存して共有できる.\n共有したいダッシュボード右上のShareボタンからSnapshotを選択する.\nSnapshot name(共有するSnapshotの名前)とExpire(Snapshotの有効期限),\nTimeout(メトリクス取得のタイムアウト秒数)が設定できるけど,\n特に何も変えずにLocal Snapshotを選択する.\nSnapshotのURLが生成される.\nただしこの状態ではホストが localhost になっているので注意.\nSnapshotの一覧は画面左のメニューからDashboards-\u0026gt;Snapshotsで確認できる.\nここで表示されるURLを使うのがかんたん.\nログインしてたときのキャッシュを使わないようにシークレットブラウザでSnapshotのURLを開いてみると,\n今度はログインなしでダッシュボードが開ける.\nただしSnapshotは作成時に時間の範囲が固定されてしまうので,\n画像のように範囲を変えるとメトリクスを見ることができない\u0026hellip;\nanonymous accessを有効にする Snapshotは簡単に共有できるので便利だけど,\nやっぱり普通のダッシュボードが見たいのでそもそものログイン設定をいじってみる.\nGrafanaの設定ファイル(grafana.ini)を編集する.\n# Grafana設定ファイルを編集して再起動 $ sudo vim /etc/grafana/grafana.ini $ sudo systemctl restart grafana-server 設定はこんな感じ.\nだいたい 290 行目あたりにAnonymous Authの設定があるので変更する.\ngrafana.ini(抜粋) #################################### Anonymous Auth ###################### [auth.anonymous] # enable anonymous access # ゲストユーザー(ログインなし)のアクセスを許可する enabled = true # specify organization name that should be used for unauthenticated users # Organization名を指定する(Main Org.はデフォルトで作成されている) org_name = Main Org. # specify role for unauthenticated users # ゲストユーザーのRole(役割)をViewer(閲覧のみ)にする # Editor(編集者), Admin(管理者)も設定可能 org_role = Viewer 再起動した後, Snapshotを使うと同様にシークレットブラウザでGrafanaのURLを開くと 今度はログインなしでダッシュボードが開かれる.\nこちらは元のダッシュボードそのものなので時間の範囲を変更してもメトリクスは問題なく表示できるが,\nRoleがViewerなので編集ができなくなっていて, Data SourceやQueryも見えなくなっている.\n編集のためにログインしたい場合は左下のSign inからログイン画面が開くので,\nそこからログインすれば今まで通り編集ができるようになる.\nこれで閲覧のみの場合はログインなしでGrafanaに入れるようになった.\nやったぜ.\nOrganizationを分ける 以上の手順でanonymous accessを許可すると,\n指定したOrganizationのダッシュボードがすべてログインなしで閲覧可能になってしまう.\nそのため, 作成途中のダッシュボードやあまり他の人に知られたくないメトリクスの場合はOrganizationを分けて管理することにする.\nOrganizationの作成はUIから簡単にできる.\nAdmin権限でログインした状態で画面左のメニューからServer Admin-\u0026gt;Orgs-\u0026gt;New Orgと進み,\nOrg. nameに任意のOrganization名を入力してCreateするだけ.\nまたは, GrafanaのAPIを使って作成することもできる.\n初期設定の場合はbasic認証がかけられているのでAdmin権限ユーザーのIDとパスワードで突破する.\n$ curl -X POST http://\u0026lt;GrafanaのURL\u0026gt;/api/orgs \\ -u \u0026lt;Admin権限のuser\u0026gt;:\u0026lt;password\u0026gt; \\ -H \u0026#34;Accept: application/json\u0026#34; \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -d \u0026#39;{\u0026#34;name\u0026#34;:\u0026#34;Org2\u0026#34;}\u0026#39; {\u0026#34;message\u0026#34;:\u0026#34;Organization created\u0026#34;,\u0026#34;orgId\u0026#34;:3} 新しく作ったOrganization(Org1)でダッシュボードを作成して保存する.\n再度Grafanaの設定を変更する.\n# Grafana設定ファイルを編集して再起動 $ sudo vim /etc/grafana/grafana.ini $ sudo systemctl restart grafana-server grafana.ini(抜粋) #################################### Anonymous Auth ###################### [auth.anonymous] # enable anonymous access enabled = true # specify organization name that should be used for unauthenticated users # 新しく作ったOrganizationを指定 org_name = Org1 # specify role for unauthenticated users org_role = Viewer これまでと同様にシークレットブラウザでGrafanaを開くと同様にログインなしで入れるが,\n今度は Org1 のダッシュボードしか見られないようになっている.\n元々のOrganization(Main Org.)を開きたい場合はログインした状態で\n左下のユーザーアイコンからSwitch Organization-\u0026gt;Switch toで切り替えられる.\nこれでログインなしで見られるダッシュボードを管理することができた.\nやったぜ.\nおわり これでGrafanaのダッシュボードをログインなしで見られるようになった.\n設定自体は3行だけでできてめっちゃ簡単だったし,\nダッシュボードが完成した後はあまりいじることもないので基本はこの設定にしておくのが便利だと思った.\nおまけ アンモニャイト\n参考 Snapshotを使う https://grafana.com/docs/grafana/latest/reference/share_dashboard/ anonymous accessを有効にする https://grafana.com/docs/grafana/latest/auth/overview/#anonymous-authentication Organizationを分ける https://grafana.com/docs/grafana/latest/http_api/org/#create-organization ","date":"2020-05-08T20:06:38+09:00","image":"/post/2020-05-08-grafana-anonymous-authentication/sotochan.jpg","permalink":"/post/2020-05-08-grafana-anonymous-authentication/","title":"Grafanaをログイン(パスワード)なしで見られるようにする"},{"content":"成果物をデプロイする 前回の続き.\nAngularの入門をやってみたので, その成果物をデプロイした.\nやったことのまとめ 作成済みのAngularアプリを持ってきてローカルでビルドした ビルドしたアプリをnginxで公開した アプリをGitHub Pagesで公開した angular-cli-ghpagesを利用したデプロイを試した つかうもの macOS Mojave 10.14 環境構築済み Angular CLI https://cli.angular.io/ バージョン: 9.1.4 インストール済み nginx https://www.nginx.com/ nginx/1.17.8 brewでインストール済み angular-cli-ghpages https://www.npmjs.com/package/angular-cli-ghpages \u0026ldquo;version\u0026rdquo;: \u0026ldquo;0.6.2\u0026rdquo; 今回入れる GitHubのリポジトリ https://github.com/uzimihsr/angular-first-app 今回作成する やったこと Angularアプリのビルド nginxでデプロイ GitHub Pagesでデプロイ 手動でやる場合 パッケージを利用する場合 Angularアプリのビルド Angular公式の入門でStackBlitz上で作ったアプリがダウンロードできるようになっているので,\nこれを試しにビルドしてみる.\nサンプルアプリを作ったときと同じように, 新たにワークスペースを作成する.\n今回はサンプルアプリは作らず空のワークスペースを作ってみる.\n# 空のワークスペースを作成 $ cd workspace $ ng new --create-application=false angular-first-app $ ls angular-first-app README.md angular.json node_modules package-lock.json package.json tsconfig.json tslint.json 自分の作ったStackBlitzプロジェクトからDownload Projectしてきた \u0026lt;プロジェクトID\u0026gt;.angular をコピーして,\n実際にアプリを立ち上げてみる.\n# 入門で作ったプロジェクトをコピー $ cd angular-first-app $ cp -rf ~/Downloads/\u0026lt;プロジェクトID\u0026gt;.angular/* ./ # そのまま起動すると依存関係が足りなくて失敗する $ ng serve An unhandled exception occurred: Cannot find module \u0026#39;@angular-devkit/build-angular/package.json\u0026#39; Require stack: ... See \u0026#34;/private/var/folders/t7/qck11mhn5fj4q6r1mbdf2nxw0000gn/T/ng-D1yxxF/angular-errors.log\u0026#34; for further details. # package.jsonに記述された依存パッケージをインストールしてから再度起動 $ npm install $ ng serve ... ** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ ** : Compiled successfully. # 動作確認ができたらCtrl+Cで終了する ブラウザで http://localhost:4200/ を開くと入門でつくったものと同じアプリが起動していることが確認できる.\nこれでアプリの動作確認はできたので, 実際にビルドしてみる.\nビルドが成功すると成果物として dist ディレクトリが作成されていることがわかる.\n# ビルド前の状態 $ cd angular-first-app $ ls README.md karma.conf.js package-lock.json src tsconfig.json tslint.json angular.json node_modules package.json tsconfig.app.json tsconfig.spec.json # アプリをビルド $ ng build --prod Generating ES5 bundles for differential loading... ES5 bundle generation complete. ... Date: 2020-05-04T01:48:14.298Z - Hash: 19cf3332dd4d450b70af - Time: 39294ms # ビルド後 $ ls README.md dist node_modules package.json tsconfig.app.json tsconfig.spec.json angular.json karma.conf.js package-lock.json src tsconfig.json tslint.json $ ls dist 3rdpartylicenses.txt main-es2015.6d0587fd878af4417329.js polyfills-es5.30e587ebdc07016ad8d1.js styles.c7ea3b8058a0e880ad91.css assets main-es5.6d0587fd878af4417329.js runtime-es2015.1eba213af0b233498d9d.js index.html polyfills-es2015.f8d7ae8b8a28c567fae7.js runtime-es5.1eba213af0b233498d9d.js Angularアプリをデプロイするときはこの dist/index.html をwebサーバーで公開すれば良いらしい.\nnginxでデプロイ まずはビルドした成果物をnginxで公開してみる.\n必要な作業は作成された dist ディレクトリをドキュメントルートに設定するだけ. かんたん.\n# 絶対パスを確認 $ cd dist $ pwd /path/to/angular-first-app/dist # nginx設定ファイルを編集して起動 $ vim /usr/local/etc/nginx/nginx.conf $ nginx # 動作確認が終わったら止める $ nginx -s stop nginx.conf worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 8080; server_name localhost; location / { # Angularアプリのdistディレクトリを指定 root /path/to/angular-first-app/dist; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } include servers/*; } nginxが問題なく動いたらブラウザで http://localhost:8080/ を開く.\nng serveしたときと同じアプリが動いていることが確認できる.\n今回はMacのnginxだったので手動で止めたけど,\n本番環境でnginxがdaemon化されている場合も同様にnginx.confをいじればアプリがデプロイできる. はず.\nGitHub Pagesでデプロイ 自分でwebサーバーを管理するのが面倒な場合はGitHub Pagesを使うこともできる.\nデプロイ方法は2通り.\n手動でやる場合 まずはGitHub Pagesの公開に必要なリポジトリ(angular-first-app)をここから作成する.\nInitialize this repository with a READMEのチェックは外しておく.\n今回作ったリポジトリ : https://github.com/uzimihsr/angular-first-app\nこのリポジトリにpushしたファイルがGitHub Pagesとして公開されるので,\nAngularアプリのビルドで作成したディレクトリ(angular-first-app)をこのリポジトリに紐付ける.\n# ng new した時点で.gitが作成されているのでinitはたぶん不要 $ cd angular-first-app $ ls -a . .editorconfig .gitignore angular.json karma.conf.js package-lock.json src tsconfig.json tslint.json .. .git README.md dist node_modules package.json tsconfig.app.json tsconfig.spec.json # リポジトリを紐付けて確認 $ git remote add origin https://github.com/uzimihsr/angular-first-app.git $ git remote -v origin\thttps://github.com/uzimihsr/angular-first-app.git (fetch) origin\thttps://github.com/uzimihsr/angular-first-app.git (push) # 一旦commitしておく $ git add . $ git commit -m \u0026#34;initial commit\u0026#34; この状態でAngularアプリをGitHub Pages用にビルドする.\n今回は--output-pathオプションを指定しているのでビルドした成果物が dist ではなく別のディレクトリ docs に作成される.\nまた, https://[GitHubアカウント].github.io/[リポジトリ名]/ でアプリにアクセスできるように--base-hrefオプションもつけている.\n# ビルド前の状態 $ ls README.md dist node_modules package.json tsconfig.app.json tsconfig.spec.json angular.json karma.conf.js package-lock.json src tsconfig.json tslint.json # 成果物の出力先とアクセスされるときのパスを指定してビルド $ ng build --prod --output-path docs --base-href /angular-first-app/ Generating ES5 bundles for differential loading... ES5 bundle generation complete. ... Date: 2020-05-04T05:50:14.401Z - Hash: 19cf3332dd4d450b70af - Time: 19395ms # ビルド後の状態 $ ls README.md dist karma.conf.js package-lock.json src tsconfig.json tslint.json angular.json docs node_modules package.json tsconfig.app.json tsconfig.spec.json # GitHub Pages用に404ページを作成 $ cp docs/index.html docs/404.html ここまでできたら, すべての変更をGitHubのリポジトリに反映する.\n# すべての変更をcommitしてpush $ git add . $ git commit -m \u0026#34;build\u0026#34; $ git push origin master 問題なくpushできたので次にGitHub Pagesの設定を行う.\nブラウザでリポジトリのsettingsを開く.\nGitHub Pagesの設定でSourceをmaster branch /docs folderに変更する.\nこれにより docs の内容がGitHub Pagesとして公開される.\n設定反映後以下のようになっていればOK.\nブラウザで https://uzimihsr.github.io/angular-first-app/ を開くと,\nng serveしたときやnginxでデプロイしたときと同じアプリがGitHub Pagesで公開されているのが確認できる.\nパッケージを利用する場合 angular-cli-ghpagesを使うことで,\n手動でやる場合よりも簡単にデプロイできる.\n最初に1回手動でデプロイしたあとはこっちの方法でデプロイするのがよさそうなので,\n手動でやる場合で作成したリポジトリをそのまま利用する.\nやることとしてはGitHub Pagesにデプロイする用のパッケージangular-cli-ghpagesを追加して,\nng deployするだけ. かんたん.\n# リモートリポジトリの確認 $ cd angular-first-app $ git remote -v origin\thttps://github.com/uzimihsr/angular-first-app.git (fetch) origin\thttps://github.com/uzimihsr/angular-first-app.git (push) # パッケージを追加 $ ng add angular-cli-ghpages Installing packages for tooling via npm. Installed packages for tooling via npm. UPDATE angular.json (3753 bytes) # デプロイ $ ng deploy --base-href=/angular-first-app/ 📦 Building \u0026#34;angular.io-example\u0026#34;. Configuration: \u0026#34;production\u0026#34;. Your base-href: \u0026#34;/angular-first-app/\u0026#34; Generating ES5 bundles for differential loading... ES5 bundle generation complete. ... Date: 2020-05-04T06:45:54.460Z - Hash: 19cf3332dd4d450b70af - Time: 20338ms 👨‍🚀 Uploading via git, please wait... 🚀 Successfully published via angular-cli-ghpages! Have a nice day! # リモートリポジトリにmasterブランチの他にgh-pagesブランチが作成されている $ git branch -a * master remotes/origin/gh-pages remotes/origin/master ng deployが成功するとリポジトリに新しく gh-pages ブランチが作成されている.\nhttps://github.com/uzimihsr/angular-first-app/tree/gh-pages\n中身を見ればなんとなくわかるが, 手動でやる場合でビルドした docs の中身と同じものがブランチの直下に作成されている.\ncommitとpushも自動でやってくれてるっぽい.\nこの gh-pages ブランチをGitHub Pagesとして公開するために再度設定を行う.\nブラウザでリポジトリのsettingsを開く.\nGitHub Pagesの設定でSourceをgh-pages branchに変更する.\n設定反映後以下のようになっていればOK.\n再度ブラウザで https://uzimihsr.github.io/angular-first-app/ を開くと,\nこれまでと同じアプリがGitHub Pagesで公開されているのが確認できる.\nこれでGitHub Pagesへのデプロイが簡単になった.\nやったぜ.\nおわり 以上の手順でAngularのアプリをnginxやGitHub Pagesに公開することができた.\n基本的にはローカルでつくったものをng serveで動作確認して,\n問題なければng deployでGitHub Pagesにデプロイ,\nもしくはng buildでビルドしたものを本番環境(nginx)にデプロイするという流れで開発ができそう.\nこれで一通り開発のやり方もわかったのでフロントエンド開発をがんばっていきたい.\nおまけ 参考 Angularアプリのビルド https://angular.jp/start/start-deployment https://angular.jp/guide/build nginxでデプロイ http://nginx.org/en/docs/beginners_guide.html#static GitHub Pagesにデプロイ https://angular.jp/guide/deployment#deploy-to-github-pages https://help.github.com/en/github/working-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site https://www.npmjs.com/package/angular-cli-ghpages#-quick-start-local-development https://www.npmjs.com/package/angular-cli-ghpages#--base-href ","date":"2020-05-04T15:43:39+09:00","image":"/post/2020-05-04-angular-deploy-fix/sotochan.jpg","permalink":"/post/2020-05-04-angular-deploy-fix/","title":"AngularアプリをnginxとGitHub Pagesでデプロイする"},{"content":"フロントエンド開発したい GWだけど特に遊ぶ予定もないので普段あまりやらないフロントエンドの開発をやってみようと思った.\nまずは環境構築からやってみた.\nやったことのまとめ anyenvとnodenvでNode.jsの環境構築をした Angular CLIをインストールした VSCodeをインストールしてExtensionを入れた つかうもの macOS Mojave 10.14 anyenv https://github.com/anyenv/anyenv anyenv 1.1.1 インストール済み nodenv https://github.com/nodenv/nodenv nodenv 1.3.2+2.2578d8d 今回入れる Angular CLI https://cli.angular.io/ バージョン: 9.1.4 今回入れる Visual Studio Code https://code.visualstudio.com/ Version: 1.44.2 今回入れる やったこと Node.jsのインストール Angular CLIのインストール VSCodeのインストール Node.jsのインストール nodenvはNode.jsのバージョン管理をやってくれるやつ.\nこれがなくても困らないけど, 入れておくとバージョン更新でトラブったときとかに多分便利.\nまずはanyenvでnodenvをインストールしてみる.\nめちゃくちゃかんたん. anyenv神.\n# anyenvでインストールできる**envの確認 $ anyenv install -l | grep nodenv nodenv # nodenvのインストール $ anyenv install nodenv ... Install nodenv succeeded! Please reload your profile (exec $SHELL -l) or open a new session. $ exec $SHELL -l # 動作確認 $ nodenv nodenv 1.3.2+2.2578d8d Usage: nodenv \u0026lt;command\u0026gt; [\u0026lt;args\u0026gt;] Some useful nodenv commands are: commands List all available nodenv commands local Set or show the local application-specific Node version global Set or show the global Node version shell Set or show the shell-specific Node version install Install a Node version using node-build uninstall Uninstall a specific Node version rehash Rehash nodenv shims (run this after installing executables) version Show the current Node version and its origin versions List installed Node versions which Display the full path to an executable whence List all Node versions that contain the given executable See \u0026#39;nodenv help \u0026lt;command\u0026gt;\u0026#39; for information on a specific command. For full documentation, see: https://github.com/nodenv/nodenv#readme nodenvがインストールできたので, 今度はこれを使ってNode.jsをインストールする.\nNode.jsはとんでもない数のバージョンがあるんだけど,\n初心者でよくわかんないので公式で現在(2020年5月4日)推奨版とされている 12.16.3 を入れる.\n# nodenvでインストール可能できるバージョンの一覧を確認 $ nodenv install -l | grep -e \u0026#34;^12.*$\u0026#34; 12.0.0 12.x-dev 12.x-next 12.1.0 12.2.0 12.3.0 12.3.1 12.4.0 12.5.0 12.6.0 12.7.0 12.8.0 12.8.1 12.9.0 12.9.1 12.10.0 12.11.0 12.11.1 12.12.0 12.13.0 12.13.1 12.14.0 12.14.1 12.15.0 12.16.0 12.16.1 12.16.2 12.16.3 # Node.js 12.16.3をインストール $ nodenv install 12.16.3 ... nodenv: default-packages file not found # なんか怒られたので対処する # nodenv installしたときに自動で入れるパッケージを指定するファイルが必要らしいので空ファイルを作る $ touch $(nodenv root)/default-packages # 再度インストール $ nodenv install 12.16.3 ... Installed node-v12.16.3-darwin-x64 to /Users/uzimihsr/.anyenv/envs/nodenv/versions/12.16.3 Installed default packages for 12.16.3 # インストール済みのバージョンを確認し使用するバージョンを指定 $ nodenv versions 12.16.3 $ nodenv global 12.16.3 $ nodenv versions * 12.16.3 (set by /Users/uzimihsr/.anyenv/envs/nodenv/version) # 動作確認 $ exec $SHELL -l $ node -v v12.16.3 $ npm -v 6.14.4 以上でNode.jsのインストールは完了.\nめっちゃ簡単だった. nodenvも神.\nついでに入ってるnpmはNode.jsのパッケージ管理ツールで,\npythonでいうpipみたいなやつ.\n(そういえば最近全然python触ってないな\u0026hellip;)\nAngular CLIのインストール 続いてAngularの開発をするためにnpmを使ってCLIをインストールする.\n無くても開発はできるみたいだけど, 入れない理由はない.\n# Angular CLIのインストール $ npm install -g @angular/cli ... /Users/uzimihsr/.anyenv/envs/nodenv/versions/12.16.3/bin/ng -\u0026gt; /Users/uzimihsr/.anyenv/envs/nodenv/versions/12.16.3/lib/node_modules/@angular/cli/bin/ng \u0026gt; @angular/cli@9.1.4 postinstall /Users/uzimihsr/.anyenv/envs/nodenv/versions/12.16.3/lib/node_modules/@angular/cli \u0026gt; node ./bin/postinstall/script.js ... + @angular/cli@9.1.4 added 271 packages from 206 contributors in 24.329s # 動作確認 $ exec $SHELL -l $ ng version ... Angular CLI: 9.1.4 Node: 12.16.3 OS: darwin x64 Angular: ... Ivy Workspace: Package Version ------------------------------------------------------ @angular-devkit/architect 0.901.4 @angular-devkit/core 9.1.4 @angular-devkit/schematics 9.1.4 @schematics/angular 9.1.4 @schematics/update 0.901.4 rxjs 6.5.4 CLIがインストールできたので, 実際にAngularアプリを動かしてみる.\nCLIはngで呼び出せる.\n# 適当なディレクトリで作業 $ cd workspace # 新規ワークスペースとサンプルアプリを作成 # パッケージをいくつか入れるので時間がかかる $ ng new my-app ? Would you like to add Angular routing? Yes ? Which stylesheet format would you like to use? CSS CREATE my-app/README.md (1022 bytes) ... ✔ Packages installed successfully. Successfully initialized git. # 作成されたディレクトリでアプリを起動する $ cd my-app $ ng serve ... ** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ ** : Compiled successfully. # 動作確認が終わったらCtrl+Cで終了 ブラウザで http://localhost:4200/ を開く.\nサンプルアプリが起動していることが確認できる.\nこれでAngularのアプリを動かせるようになった.\nやったぜ.\nVSCodeのインストール 普段開発用のエディタはAtomを使ってるんだけど,\nAngular向けの良いパッケージが見つからなかったのでVisual Studio Code(VSCode)を使ってみる.\nホントはAtomで頑張りたかったんだけど\n公式のIDEリストでも推奨されてるし,\nチュートリアルとかで使ってるStackBlitzもVSCodeっぽいIDEなので逆らえなかった.\n普通に公式のダウンロードページから落としてくる.\n勝手に展開されるので, Visual Studio Code.app を /Applications に移動する.\nとは言っても毎回アプリを探して起動するのは不便なのでPATHを通してコマンドラインから起動できるようにする.\n実際に起動し, F1でコマンドパレットを開きInstall 'code' command in PATHを選択する.\n画面右下にShell command 'code' successfully installed in PATH.と表示されれば設定は完了.\n試しに先程作成したワークスペースをコマンドラインから開いてみる.\n$ cd my-app # VSCodeでカレントディレクトリを開く $ code . さらにAngularを扱いやすくするために公式のExtensionを入れておく.\nAtomでいうパッケージみたいな拡張機能をVSCodeではExtensionと呼ぶらしい.\nメニューから検索すればすぐ見つかるのでInstallを押せば入る.\nこれでエディタのセットアップも完了.\nやったぜ.\nおわり 以上の手順でMacにAngularアプリの開発環境をつくることができた.\nフロントエンド初心者だけどちょっとずつ頑張っていきたい.\n実際の成果物をデプロイする手順は長くなりそうなので別の記事に書く.\nおまけ 参考 Node.jsのインストール https://github.com/nodenv/nodenv/blob/master/README.md#installing-node-versions Angular CLIのインストール https://angular.jp/cli https://angular.jp/start/start-deployment https://angular.jp/guide/setup-local VSCodeのインストール https://code.visualstudio.com/docs/setup/mac#_launching-from-the-command-line ","date":"2020-05-03T18:43:39+09:00","image":"/post/2020-05-03-angular-setup/sotochan.jpg","permalink":"/post/2020-05-03-angular-setup/","title":"MacでAngularの開発環境構築"},{"content":"ジョブの監視 Pushgatewayを使った監視をやってみた.\nやったことのまとめ ラズパイにPushgatewayをインストールした KubernetesのCronJobからメトリクスをPushしてみた 簡単な監視ルールを設定した つかうもの Raspberry Pi 3 Model B+ OSはRaspbian(10.0) 監視サーバとして使用 Prometheus version=\u0026ldquo;2.15.2\u0026rdquo; インストール済み Pushgateway version=\u0026ldquo;1.2.0\u0026rdquo; 今回入れる macOS Mojave 10.14 Go, Docker, Kubernetesはこちらで実行 Go(golang) go version go1.13 Docker Version: 19.03.2 Kubernetesクラスタ(Minikube) GitVersion:\u0026ldquo;v1.16.2\u0026rdquo; MacBook上で構築 構成 Pushgatewayとは\u0026hellip;\n\u0026ldquo;The Pushgateway is an intermediary service which allows you to push metrics from jobs which cannot be scraped.\u0026rdquo;\n\u0026ldquo;Pushgatewayとは, スクレイプが不可能なジョブのメトリクスをプッシュするための仲介サービスです.\u0026rdquo;\n(https://prometheus.io/docs/practices/pushing/ より超意訳)\nNode exporterみたいなExporterは監視対象が動いてる間メトリクスを吐き出しつづけるからPrometheusで定期的にスクレイプ(pull)できるけど,\nバッチジョブみたいに動いている間しか情報を持たないものはPrometheusからスクレイプできず, 通常の方法では監視ができない.\nこれを解決するため, Pushgatewayを使ってジョブからのメトリクスのpushを受け付けて永続化し, Prometheusでこのメトリクスをpullすることでジョブの監視を行う.\nやったこと Pushgatewayのセットアップ メトリクスのpushとPrometheusによるスクレイプ CronJobの監視 Pushgatewayのセットアップ まずはラズパイにPushgatewayをインストールしていく.\nダウンロードページでArchitectureをarmv7にした状態で\npushgateway-1.2.0.linux-armv7.tar.gz のダウンロードリンクを確認する.\nラズパイにSSHしてダウンロード, インストール, 起動までやってみる.\n# 以下はRaspberry Piで実行 # 任意のディレクトリで作業 $ cd workspace # 確認したURLからダウンロードして展開, 移動 $ wget https://github.com/prometheus/pushgateway/releases/download/v1.2.0/pushgateway-1.2.0.linux-armv7.tar.gz $ tar -xzf pushgateway-1.2.0.linux-armv7.tar.gz $ sudo cp pushgateway-1.2.0.linux-armv7/pushgateway /usr/local/bin/ # 起動してみる $ /usr/local/bin/pushgateway level=info ts=2020-04-28T13:24:39.058Z caller=main.go:83 msg=\u0026#34;starting pushgateway\u0026#34; version=\u0026#34;(version=1.2.0, branch=HEAD, revision=b7e0167e9574f4f88404dde9653ee1d3c940f2eb)\u0026#34; level=info ts=2020-04-28T13:24:39.058Z caller=main.go:84 build_context=\u0026#34;(go=go1.13.8, user=root@0e823ccfff84, date=20200311-18:57:04)\u0026#34; level=info ts=2020-04-28T13:24:39.062Z caller=main.go:137 listen_address=:9091 # 確認次第Ctrl+Cで終了する [ラズパイのIP]:9091 を開いてみるとPushgatewayのUI画面が開く.\n今のところは何もメトリクスがないのでヘッダ以外は真っ白な画面.\n動作確認ができたので,\nサービス化してついでにnginxでのリバースプロキシにも対応させておく.\n# 以下はRaspberry Piで実行 # service起動に必要なuser(pushgateway)を追加する $ sudo useradd -U -s /sbin/nologin -M -d / pushgateway # serviceファイルの作成 $ sudo vim /etc/systemd/system/pushgateway.service # serviceの自動起動設定と起動 $ sudo systemctl daemon-reload $ sudo systemctl enable pushgateway.service Created symlink /etc/systemd/system/multi-user.target.wants/pushgateway.service → /etc/systemd/system/pushgateway.service. $ sudo systemctl start pushgateway.service ● pushgateway.service - Pushgateway Loaded: loaded (/etc/systemd/system/pushgateway.service; enabled; vendor preset: enabled) Active: active (running) since Tue 2020-04-28 22:41:04 JST; 28s ago Main PID: 30393 (pushgateway) Tasks: 8 (limit: 2200) Memory: 3.2M CGroup: /system.slice/pushgateway.service └─30393 /usr/local/bin/pushgateway --web.external-url=http://localhost:8080/pushgateway/ --web.route-prefix=/ ... Apr 28 22:41:04 raspberrypi pushgateway[30393]: level=info ts=2020-04-28T13:41:04.858Z caller=main.go:137 listen_address=:9091 # nginx設定ファイルの変更, 再起動 $ sudo vim /etc/nginx/conf.d/default.conf $ sudo systemctl restart nginx pushgateway.service [Unit] Description=Pushgateway [Service] User=pushgateway ExecStart=/usr/local/bin/pushgateway \\ --web.external-url=http://localhost:8080/pushgateway/ \\ --web.route-prefix=/ [Install] WantedBy=multi-user.target default.conf server { listen\t8080; location /prometheus/ { proxy_pass\thttp://localhost:9090/; } location /alertmanager/ { proxy_pass\thttp://localhost:9093/; } location /pushgateway/ { proxy_pass\thttp://localhost:9091/; } location /grafana/ { proxy_pass\thttp://localhost:3000/; } } ここまで終わらせれば [ラズパイのIP]:[nginxのポート]/pushgateway でPushgatewayのUIが開けるようになっているはず.\n以上でPushgatewayのセットアップは完了.\nメトリクスのpushとPrometheusによるスクレイプ Pushgatewayの準備ができたので, 実際にメトリクスをpushしてみる.\n各言語のクライアントを使った叩き方はここにあるけど,\n今回は敢えてAPIを使ってコマンドラインからpushしてみる.\n# 以下はMacで実行 # job=\u0026#34;some_job\u0026#34;のグループにsome_metricという名前のGaugeをpushする $ echo \u0026#34;some_metric 3.14\u0026#34; | curl --data-binary @- http://\u0026lt;ラズパイのIP\u0026gt;:\u0026lt;nginxのポート\u0026gt;/pushgateway/metrics/job/some_job Pushが成功していればPushgatewayのUIにメトリクスの情報が表示される.\nsome_metric以外の2つのメトリクス(push_time_seconds, push_failure_time_seconds)はそれぞれ\nメトリクスの更新に(成功|失敗)したUNIX時間を値として持つメトリクス.\nこんな感じでジョブ実行時にpushすることで, Pushgatewayにジョブのメトリクスが貯まっていく.\nこれらのメトリクスを時系列データとして扱うため,\nPrometheusでPushgatewayの持つメトリクスを定期的に収集(スクレイプ)する設定を追加してみる.\nラベルの衝突を避けるため, Pushgatewayのスクレイプ設定にはhonor_labelsを追加しておく.\n# 以下はRaspberry Piで実行 # Prometheus設定ファイルを編集, 再起動 $ sudo vim /usr/local/prometheus/prometheus.yml $ sudo systemctl restart prometheus.service prometheus.yml global: scrape_interval: 15s evaluation_interval: 15s rule_files: - rules.yml alerting: alertmanagers: - static_configs: - targets: [\u0026#39;localhost:9093\u0026#39;] scrape_configs: - job_name: \u0026#39;prometheus\u0026#39; static_configs: - targets: [\u0026#39;localhost:9090\u0026#39;] - job_name: \u0026#39;node\u0026#39; static_configs: - targets: [\u0026#39;localhost:9100\u0026#39;] # ここから下を追加 - job_name: \u0026#39;pushgateway\u0026#39; honor_labels: true static_configs: - targets: [\u0026#39;localhost:9091\u0026#39;] # Pushgatewayが起動しているポートを指定 設定が問題なく反映されていれば,\nPrometheusからPushgatewayがスクレイプできているのを確認できる.\n試しに何回かメトリクスをpushして, それからPrometheusで時系列データを確認してみる.\n# 以下はMacで実行 # 先程とは違う値をpush $ echo \u0026#34;some_metric 1.41\u0026#34; | curl --data-binary @- http://\u0026lt;ラズパイのIP\u0026gt;:\u0026lt;nginxのポート\u0026gt;/pushgateway/metrics/job/some_job # 少し待ってから再度push $ echo \u0026#34;some_metric 2.71\u0026#34; | curl --data-binary @- http://\u0026lt;ラズパイのIP\u0026gt;:\u0026lt;nginxのポート\u0026gt;/pushgateway/metrics/job/some_job some_metricの値を見てみると, 次のようになる.\n注意すべき点としては, 新たにメトリクスがpushされるまで以前の値が変わらず保持され続けること.\nPushgatewayはあくまでジョブのメトリクスを受け付けるためのものなので,\nジョブが実行されていない間は最後にpushされた値を保持し続けてPrometheusがスクレイプできるようにしている.\nこれでジョブのメトリクスをPushgateway経由でPrometheusがスクレイプできるようになった.\nCronJobの監視 ジョブのメトリクスが収集できるようになったので, いよいよ簡単な監視をしてみる.\nまずは監視対象としてGoで乱数のメトリクスをpushするだけのサンプルジョブを作成し,\nこの手順でDocker image化してみる.\nPrometheusクライアントの使い方はpushパッケージのGoDocを参考にした.\n# 以下はMacで実行 # ジョブプログラムの作成 $ vim main.go # 動作確認 $ go run main.go --endpoint=192.168.3.200:9091 192.168.3.200:9091 sample_job Metrics pushed successfully. # docker化 $ vim Dockerfile $ docker image build -t golang-sample-job:latest . ... Successfully built ff903de0d164 Successfully tagged golang-sample-job:latest $ docker image ls golang-sample-job REPOSITORY TAG IMAGE ID CREATED SIZE golang-sample-job latest ff903de0d164 About a minute ago 12.6MB # 動作確認 $ docker container run --rm golang-sample-job:latest --endpoint=192.168.3.200:9091 192.168.3.200:9091 sample_job Metrics pushed successfully. # タグをつけ直してDockerHubにアップロード $ docker image tag golang-sample-job:latest uzimihsr/golang-sample-job:latest $ docker image push uzimihsr/golang-sample-job:latest main.go package main import ( \u0026#34;flag\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;math/rand\u0026#34; \u0026#34;os\u0026#34; \u0026#34;time\u0026#34; \u0026#34;github.com/prometheus/client_golang/prometheus\u0026#34; \u0026#34;github.com/prometheus/client_golang/prometheus/push\u0026#34; ) func main() { // Pushgatewayのエンドポイントとジョブ名は実行時引数で渡す var pushgatewayEndpoint = flag.String(\u0026#34;endpoint\u0026#34;, \u0026#34;http://pushgateway:9091\u0026#34;, \u0026#34;Pushgateway endpoint. default: http://pushgateway:9091\u0026#34;) var jobName = flag.String(\u0026#34;job\u0026#34;, \u0026#34;sample_job\u0026#34;, \u0026#34;Job name. default: sample_job\u0026#34;) flag.Parse() if flag.NFlag() \u0026gt; 2 { fmt.Println(\u0026#34;flags : --endpoint, --job\u0026#34;) os.Exit(1) } fmt.Println(*pushgatewayEndpoint) fmt.Println(*jobName) // Gaugeの作成 randomValue := prometheus.NewGauge(prometheus.GaugeOpts{ Name: \u0026#34;random_value\u0026#34;, Help: \u0026#34;Float64 random value generated by golang.\u0026#34;, }) // 乱数をGaugeにセット rand.Seed(time.Now().UnixNano()) randomValue.Set(rand.Float64()) // メトリクスをpush if err := push.New(*pushgatewayEndpoint, *jobName). Collector(randomValue). Grouping(\u0026#34;sample_label\u0026#34;, \u0026#34;sample_label_value\u0026#34;). Push(); err != nil { fmt.Println(\u0026#34;Could not push metrics to Pushgateway:\u0026#34;, err) os.Exit(1) } fmt.Println(\u0026#34;Metrics pushed successfully.\u0026#34;) } Dockerfile # build FROM golang:1.13 COPY . ./goapp WORKDIR ./goapp RUN go mod download \u0026amp;\u0026amp; \\ CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /app . # run FROM scratch LABEL maintainer=\u0026#34;usimihsr\u0026#34; WORKDIR goapp COPY --from=0 /app ./ COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt ENTRYPOINT [\u0026#34;./app\u0026#34;] 実際にビルドしたimage : uzimihsr/golang-sample-job\nこれでサンプルジョブのDocker imageが作成できたので,\n次にこれをCronJob化する.\n# 以下はMacで実行 # マニフェストの作成とCronJobの作成 $ vim cronjob.yaml $ kubectl apply -f cronjob.yaml $ kubectl get cronjob sample-cronjob NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE sample-cronjob */1 * * * * False 0 6m37s 3h12m このCronJobではPod定義の.spec.initContainersで\nメインコンテナの前にメトリクスをpushするコンテナを必ず実行するように定義している.\nこうすることでメインコンテナの成否に関わらずジョブの監視が可能になる.\ncronjob.yaml apiVersion: batch/v1beta1 kind: CronJob metadata: name: sample-cronjob spec: schedule: \u0026#34;*/1 * * * *\u0026#34; concurrencyPolicy: Allow startingDeadlineSeconds: 30 successfulJobsHistoryLimit: 5 failedJobsHistoryLimit: 3 jobTemplate: spec: completions: 1 parallelism: 1 backoffLimit: 0 template: spec: initContainers: - name: push-metrics image: uzimihsr/golang-sample-job:latest imagePullPolicy: IfNotPresent args: [\u0026#34;--endpoint=\u0026lt;ラズパイのIP\u0026gt;:9091\u0026#34;] containers: - name: main-batch image: busybox args: - /bin/sh - -c - date; echo Hello from the Kubernetes cluster restartPolicy: Never CronJobが問題なく動作していれば1分ごとのジョブ実行時に乱数のメトリクスrandom_valueがpushされるので,\nPrometheusで確認してみる.\nこれらのメトリクスを使って簡単な監視ができるので, 以下のアラートルールを作成してみる.\n# 以下はMacで実行 # アラートルールを編集して反映 $ vim /usr/local/prometheus/rules.yml $ sudo systemctl restart prometheus.service rules.yml groups: - name: instance rules: - alert: InstanceDown expr: up == 0 for: 1m # 以下のルールを追加 - name: pushgateway rules: - alert: CronJobNotScheduled expr: time() - push_time_seconds{job=\u0026#34;sample_job\u0026#34;} \u0026gt; 60 * 2 - alert: CronJobFailed expr: random_value{job=\u0026#34;sample_job\u0026#34;} \u0026gt; 0.5 for: 2m CronJobNotScheduledは最後にCronJobが実行されてからの時間が2分以上になった場合に発火するアラート.\npushの際に自動で更新されるメトリクスpush_time_secondsの値と現在の時間を比較して条件判定している.\nクラスタに障害があったりして2分以上CronJobが実行されなかった場合はメトリクスがpushされないので,\nこのルールで検知できる.\nCronJobFailedはCronJobが2分以上失敗し続けた場合に発火する(ことを想定した)アラート.\n今回は例としてジョブ開始時にpushされる乱数のメトリクスrandom_valueを条件判定に使っているが,\nジョブのプロセスの終了ステータスをメトリクス化してジョブの最後にpushさせればジョブが連続して失敗した場合に検知できる.\n試しにこのアラートルールが有効になっている状態でCronJobを停止してみる.\n# 以下はMacで実行 # CronJobを停止する $ kubectl patch cronjob sample-cronjob -p \u0026#39;{\u0026#34;spec\u0026#34;:{\u0026#34;suspend\u0026#34;:true}}\u0026#39; $ kubectl get cronjob sample-cronjob NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE sample-cronjob */1 * * * * True 0 49s 3h49m CronJobを停止すると新たなメトリクスがpushされなくなるので,\n2分以上経過した時点でCronJobNotScheduledが発火し,\n停止した時点で最後にpushされたrandom_valueがしきい値(0.5)より大きい値であったのでCronJobFailedも発火した.\nこれでPushgatewayを利用したCronJobの監視ができるようになった.\nやったぜ.\nおわり PushgatewayのインストールからCronJobの監視までの流れを一通りやってみた.\nCronJobがちゃんと実行されているかをKubernetes APIやkubectlでいちいち確認するのは大変なので,\nこんな感じで監視できると便利だと思う.\nおまけ ","date":"2020-04-30T21:40:42+09:00","image":"/post/2020-04-30-pushgateway/2020-04-30-sotochan.jpg","permalink":"/post/2020-04-30-pushgateway/","title":"PushgatewayでCronJobの監視を行う"},{"content":"Podがいっぱいあると調査するのが大変 KubernetesクラスタのPod情報を取りたいときに,\nPodの起動日時を指定して取る方法がわかんなかったのでメモ.\nまとめ Podが起動した時刻を範囲指定して情報を取得する例\n# 2020-04-15の14:01:00~14:09:00(UTC)の間に作成されたPodだけ取り出してみる $ kubectl get pods -o custom-columns=\u0026#34;NAME:{.metadata.name},CREATED-AT:{.metadata.creationTimestamp}\u0026#34; | awk \u0026#39;{if (NR==1) print; else if (\u0026#34;2020-04-15T14:01:00Z\u0026#34; \u0026lt; $2 \u0026amp;\u0026amp; $2 \u0026lt; \u0026#34;2020-04-15T14:09:00Z\u0026#34;) print}\u0026#39; NAME CREATED-AT sleep-1586959500-7pf6c 2020-04-15T14:05:06Z やりかた 事前準備 Pod情報を取得する プラグイン化してみる 事前準備 何個かPodを作っておきたいのでCronJobを作成する.\nせっかくなのでyaml無しでkubectl createでつくってみる.\n# 5分毎に起動して30秒sleepするCronJob $ kubectl create cronjob test-job --image=busybox --schedule=\u0026#34;*/5 * * * *\u0026#34; --restart=Never -- /bin/sh -c \u0026#39;date; sleep 30; date\u0026#39; cronjob.batch/sleep created $ kubectl get cronjob sleep -o yaml apiVersion: batch/v1beta1 kind: CronJob metadata: creationTimestamp: \u0026#34;2020-04-15T13:59:57Z\u0026#34; name: sleep namespace: default resourceVersion: \u0026#34;8570399\u0026#34; selfLink: /apis/batch/v1beta1/namespaces/default/cronjobs/sleep uid: a199da04-7fdc-11ea-b34b-42010a800054 spec: concurrencyPolicy: Allow failedJobsHistoryLimit: 1 jobTemplate: metadata: creationTimestamp: null name: sleep spec: template: metadata: creationTimestamp: null spec: containers: - command: - /bin/sh - -c - date; sleep 30; date image: busybox imagePullPolicy: Always name: sleep resources: {} terminationMessagePath: /dev/termination-log terminationMessagePolicy: File dnsPolicy: ClusterFirst restartPolicy: Never schedulerName: default-scheduler securityContext: {} terminationGracePeriodSeconds: 30 schedule: \u0026#39;*/5 * * * *\u0026#39; successfulJobsHistoryLimit: 3 suspend: false status: active: - apiVersion: batch/v1 kind: Job name: sleep-1587039720 namespace: default resourceVersion: \u0026#34;8570397\u0026#34; uid: df56a4d7-7fdc-11ea-b34b-42010a800054 lastScheduleTime: \u0026#34;2020-04-15T14:00:00Z\u0026#34; # 15分以上放っておくとPodが3つできている(successfulJobsHistoryLimit: 3) $ kubectl get pods | awk \u0026#39;{if (NR==1) print; else if ($1 ~ /sleep/) print}\u0026#39; NAME READY STATUS RESTARTS AGE sleep-1586959200-m8q7b 0/1 Completed 0 11m sleep-1586959500-7pf6c 0/1 Completed 0 6m14s sleep-1586959800-wgmpd 0/1 Completed 0 74s # CronJobを止めておく $ kubectl patch cronjob sleep -p \u0026#39;{\u0026#34;spec\u0026#34;:{\u0026#34;suspend\u0026#34;:true}}\u0026#39; cronjob.batch/sleep patched Pod情報を取得する 普通にgetしてみる.\n$ kubectl get pods NAME READY STATUS RESTARTS AGE sleep-1586959200-m8q7b 0/1 Completed 0 14m sleep-1586959500-7pf6c 0/1 Completed 0 9m21s sleep-1586959800-wgmpd 0/1 Completed 0 4m21s これだとAGEしかわかんないので,\n例えばhh時mm分~hh時nn分に作成されたPodだけほしいってときに困る.\nPodの.metadata.creationTimestampには起動した時間が記述されているので,\nこれを表示するようにしてみる.\n# columnにPod名と起動時間を指定してみる $ kubectl get pods -o custom-columns=\u0026#34;NAME:{.metadata.name},CREATED-AT:{.metadata.creationTimestamp}\u0026#34; NAME CREATED-AT sleep-1586959200-m8q7b 2020-04-15T14:00:05Z sleep-1586959500-7pf6c 2020-04-15T14:05:06Z sleep-1586959800-wgmpd 2020-04-15T14:10:06Z いい感じ.\nこれで各Podがいつ起動したかがわかる.\nちょっと調べた(kubectl get pods --help見ただけ)けど\n時間の範囲を指定して表示するオプションとかがなかったので,\nここからはawkでしこしこやっていく.\n.status.startTimeのフォーマットがYYYY-MM-DDThh:mm:ssZなので,\nこれに従って範囲を指定してみる.\n参考 : https://ja.wikipedia.org/wiki/ISO_8601\n# 2020-04-15の14:01:00~14:09:00(UTC)の間に作成されたPodだけ取り出してみる $ kubectl get pods -o custom-columns=\u0026#34;NAME:{.metadata.name},CREATED-AT:{.metadata.creationTimestamp}\u0026#34; | awk \u0026#39;{if (NR==1) print; else if (\u0026#34;2020-04-15T14:01:00Z\u0026#34; \u0026lt; $2 \u0026amp;\u0026amp; $2 \u0026lt; \u0026#34;2020-04-15T14:09:00Z\u0026#34;) print}\u0026#39; NAME CREATED-AT sleep-1586959500-7pf6c 2020-04-15T14:05:06Z 時刻の範囲を指定してPod名が取得できた.\nやったぜ.\nプラグイン化してみる 毎度毎度コマンドを組み立てるのも大変なので, かんたんなプラグインをつくってみる.\nkubectl-timerange\n#!/bin/sh func() { if [[ $# -lt 2 || $# -gt 3 ]]; then echo \u0026#39;args: resource time_from time_to\u0026#39; exit 1 fi local resource=\u0026#34;$1\u0026#34; local time_from=\u0026#34;$2\u0026#34; local time_to if [[ $# -lt 3 ]]; then time_to=$(date -u +%Y-%m-%dT%k:%M:%SZ) else time_to=\u0026#34;$3\u0026#34; fi kubectl get \u0026#34;${resource}\u0026#34; -o custom-columns=\u0026#34;NAME:{.metadata.name},START_TIME:{.metadata.creationTimestamp}\u0026#34; | awk \u0026#39;{if (NR==1) print; else if (\u0026#39;\\\u0026#34;${time_from}\\\u0026#34;\u0026#39; \u0026lt; $2 \u0026amp;\u0026amp; $2 \u0026lt; \u0026#39;\\\u0026#34;${time_to}\\\u0026#34;\u0026#39;) print}\u0026#39; } func \u0026#34;$@\u0026#34; 作ったプラグインを読めるようにする.\nファイル名がkubectl-hogeのスクリプトがPATHの通った場所にあると,\nkubectl hogeで呼び出すことができる.\nExtend kubectl with plugins\n# スクリプトを記述 $ vim /usr/local/bin/kubectl-timerange # 実行できるようにする $ chmod +x /usr/local/bin/kubectl-timerange # 有効なプラグインを確認する $ kubectl plugin list The following compatible plugins are available: /usr/local/bin/kubectl-timerange # 実際に使ってみる $ kubectl timerange pod \u0026#39;2020-04-15T14:01:00Z\u0026#39; \u0026#39;2020-04-15T14:09:00Z\u0026#39; NAME CREATED-AT sleep-1586959500-7pf6c 2020-04-15T14:05:06Z できた.\n毎回awkのスクリプトを書くのも大変なのでこっちのほうが使いやすい.\nはず\u0026hellip;\nおわり CronJobを扱う機会が多いので, Jobが連続して失敗したときなんかに調査用に使っていきたい.\nというかawkが便利なのでもうちょっと使いこなせるようになりたい.\nおまけ ","date":"2020-04-15T11:11:48+09:00","image":"/post/2020-04-15-kubectl/2020-04-16-sotochan.jpg","permalink":"/post/2020-04-15-kubectl/","title":"kubectlで時間範囲を指定してPod情報を取得する"},{"content":"軽いイメージをつくる Go(golang)のDocker buildの練習をしてみた.\nやったことのまとめ Go(golang)で作ったアプリをDockerイメージにした マルチステージビルドを使って軽いイメージを作った scratchを使う場合は次のようなDockerfileを書けばいい.\n# ビルド用イメージ FROM golang:1.13 # mainパッケージがあるディレクトリ(.)をまるごとコピー COPY . ./goapp WORKDIR ./goapp # goapp内のgo.mod, go.sumで依存関係を管理している場合に使用 RUN go mod download # クロスコンパイル RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /app . # バイナリを載せるイメージ FROM scratch WORKDIR goapp # ビルド済みのバイナリをコピー COPY --from=0 /app ./ # httpsで通信を行う場合に使用 COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt ENTRYPOINT [\u0026#34;./app\u0026#34;] つかうもの MacBook Pro (Retina, 15-inch, Mid 2015) macOS Mojave 10.14 Go(golang) go version go1.13 darwin/amd64 Docker Docker version 19.03.2, build 6a30dfc なんかのGoアプリ 前回作ったやつを使用 やったこと 普通にDockerイメージをビルド マルチステージビルドを試す scratchを使う 普通にDockerイメージをビルド Goのアプリはなんでもいいはずなので,\n前回作ったwikipedia検索するおもちゃをそのまま使ってみる.\nhttps://github.com/uzimihsr/wikipedia-search\nDockerfileを書くとこんな感じ.\nベースイメージはgolang:1.13でビルドしてみる.\nDockerfile-golang\n# ベースイメージが大きいパターン FROM golang:1.13 LABEL maintainer=\u0026#34;usimihsr\u0026#34; # ファイルを全部コピー COPY . ./goapp WORKDIR ./goapp # go.modとgo.sumを使って管理している依存関係をダウンロードしてビルド RUN go mod download \u0026amp;\u0026amp; \\ go build -o ./app . # バイナリをエントリーポイントに指定 ENTRYPOINT [\u0026#34;./app\u0026#34;] ビルドして動かしてみる.\n$ git clone https://github.com/uzimihsr/wikipedia-search.git $ cd wikiepdia-search # Dockerイメージをビルドする $ docker image build -t wikipedia-search:golang -f Dockerfile-golang . Sending build context to Docker daemon 87.55kB Step 1/6 : FROM golang:1.13 ---\u0026gt; 3a7408f53f79 Step 2/6 : LABEL maintainer=\u0026#34;usimihsr\u0026#34; ---\u0026gt; Using cache ---\u0026gt; b5ba8c17244f Step 3/6 : COPY . ./goapp ---\u0026gt; d2bffdafe570 Step 4/6 : WORKDIR ./goapp ---\u0026gt; Running in def85395de98 Removing intermediate container def85395de98 ---\u0026gt; d215e88c95ba Step 5/6 : RUN go mod download \u0026amp;\u0026amp; go build -o ./app . ---\u0026gt; Running in 307004980e61 Removing intermediate container 307004980e61 ---\u0026gt; 9873d8f458a6 Step 6/6 : ENTRYPOINT [\u0026#34;./app\u0026#34;] ---\u0026gt; Running in 4f189dba7fa1 Removing intermediate container 4f189dba7fa1 ---\u0026gt; f75198355e97 Successfully built f75198355e97 Successfully tagged wikipedia-search:golang # ENTRYPOINTを無視してbashを起動 # 指定したディレクトリにコピーしたファイルとビルドしたバイナリが確認できる $ docker container run --rm -it --entrypoint=\u0026#39;\u0026#39; wikipedia-search:golang /bin/bash root@6f62f4307560:/go/goapp$ pwd /go/goapp root@6f62f4307560:/go/goapp$ ls Dockerfile-golang README.md app go.mod main.go root@6f62f4307560:/go/goapp$ exit # コンテナを動かしてみる # \u0026lt;-srlimit=5 \u0026#39;イチロー\u0026#39;\u0026gt;はENTRYPOINTのバイナリ(./app)実行時に渡すパラメータ $ docker container run wikipedia-search:golang -srlimit=5 \u0026#39;イチロー\u0026#39; --------------------------------------------------- イチロー https://ja.wikipedia.org/?curid=1432262 --------------------------------------------------- 首位打者 (日本プロ野球) https://ja.wikipedia.org/?curid=38085 --------------------------------------------------- 国道262号 https://ja.wikipedia.org/?curid=126147 --------------------------------------------------- 河上イチロー https://ja.wikipedia.org/?curid=3682529 --------------------------------------------------- 新井宏昌 https://ja.wikipedia.org/?curid=688515 --------------------------------------------------- 問題なく動いた.\nマルチステージビルドを試す 上記の手順でGoアプリのイメージが問題なくビルドできた. が,\nベースイメージに使わないファイルが大量にあるため,\nかなり重いイメージ(810MB)が出来上がってしまった.\n$ docker image ls wikipedia-search REPOSITORY TAG IMAGE ID CREATED SIZE wikipedia-search golang f75198355e97 6 minutes ago 810MB 実際に使いたいのはバイナリ(app)だけなので,\nマルチステージビルドを使ってビルド済みのバイナリだけを軽いイメージに載せてみる.\n試しに軽量Linuxイメージのalpine:latestを使ってみる.\nDockerfile-alpine\n# golangをビルド用イメージとして使うパターン FROM golang:1.13 COPY . ./goapp WORKDIR ./goapp # ビルド時にクロスコンパイルのオプションを指定 RUN go mod download \u0026amp;\u0026amp; \\ CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /app . # ベースイメージはalpineを指定 FROM alpine:latest LABEL maintainer=\u0026#34;usimihsr\u0026#34; WORKDIR goapp # ビルド用イメージからバイナリをコピー COPY --from=0 /app ./ # httpsで通信するのに必要なCA証明書を用意する RUN apk --no-cache add ca-certificates ENTRYPOINT [\u0026#34;./app\u0026#34;] # ビルド $ docker image build -t wikipedia-search:alpine -f Dockerfile-alpine . Sending build context to Docker daemon 98.3kB Step 1/10 : FROM golang:1.13 ---\u0026gt; 3a7408f53f79 Step 2/10 : COPY . ./goapp ---\u0026gt; 2ce3dd5f2452 Step 3/10 : WORKDIR ./goapp ---\u0026gt; Running in 66c34bbf39c1 Removing intermediate container 66c34bbf39c1 ---\u0026gt; 6876fbc22a61 Step 4/10 : RUN go mod download \u0026amp;\u0026amp; CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /app . ---\u0026gt; Running in 98346481243b Removing intermediate container 98346481243b ---\u0026gt; 3bc411f60c04 Step 5/10 : FROM alpine:latest ---\u0026gt; 961769676411 Step 6/10 : LABEL maintainer=\u0026#34;usimihsr\u0026#34; ---\u0026gt; Running in dbddd972c873 Removing intermediate container dbddd972c873 ---\u0026gt; cb8ff63adce6 Step 7/10 : WORKDIR goapp ---\u0026gt; Running in cad6ec5d31df Removing intermediate container cad6ec5d31df ---\u0026gt; d88c328fe670 Step 8/10 : COPY --from=0 /app ./ ---\u0026gt; 5a8738b30e43 Step 9/10 : RUN apk --no-cache add ca-certificates ---\u0026gt; Running in c271e71b475e fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/community/x86_64/APKINDEX.tar.gz (1/1) Installing ca-certificates (20190108-r0) Executing busybox-1.30.1-r2.trigger Executing ca-certificates-20190108-r0.trigger OK: 6 MiB in 15 packages Removing intermediate container c271e71b475e ---\u0026gt; 7a22400f095e Step 10/10 : ENTRYPOINT [\u0026#34;./app\u0026#34;] ---\u0026gt; Running in a3f75ef34223 Removing intermediate container a3f75ef34223 ---\u0026gt; b9635a037fe6 Successfully built b9635a037fe6 Successfully tagged wikipedia-search:alpine # マルチステージビルドで置かれたバイナリを確認 $ docker container run --rm -it --entrypoint=\u0026#39;\u0026#39; wikipedia-search:alpine /bin/ash /goapp $ pwd /goapp /goapp $ ls app /goapp $ exit # アプリを実行 $ docker container run wikipedia-search:alpine -srlimit=5 \u0026#39;イチロー\u0026#39; --------------------------------------------------- イチロー https://ja.wikipedia.org/?curid=1432262 --------------------------------------------------- 首位打者 (日本プロ野球) https://ja.wikipedia.org/?curid=38085 --------------------------------------------------- 国道262号 https://ja.wikipedia.org/?curid=126147 --------------------------------------------------- 河上イチロー https://ja.wikipedia.org/?curid=3682529 --------------------------------------------------- 新井宏昌 https://ja.wikipedia.org/?curid=688515 --------------------------------------------------- 問題なく動いた.\nイメージもだいぶ軽くなった(810MB -\u0026gt; 13.5MB).\n$ docker image ls wikipedia-search REPOSITORY TAG IMAGE ID CREATED SIZE wikipedia-search alpine 9fc6e1d19baf 18 seconds ago 13.5MB wikipedia-search golang f75198355e97 52 minutes ago 810MB やったぜ.\nscratchを使う なんと! 世の中にはalpineよりもっと軽いイメージがあるらしい.\nscratchはDockerの最小イメージで, 中にはなんにも入っていない.\nこいつを使えばめちゃめちゃ軽いイメージが作れるのでは?\nやってみる.\nDockerfile-scratch\n# golangをビルド用イメージとして使うパターン FROM golang:1.13 COPY . ./goapp WORKDIR ./goapp RUN go mod download \u0026amp;\u0026amp; \\ CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /app . # ベースイメージはscratchを指定 FROM scratch LABEL maintainer=\u0026#34;usimihsr\u0026#34; WORKDIR goapp COPY --from=0 /app ./ # httpsで通信するのに必要なCA証明書を用意する COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt ENTRYPOINT [\u0026#34;./app\u0026#34;] # ビルド $ docker image build -t wikipedia-search:scratch -f Dockerfile-scratch . Sending build context to Docker daemon 98.3kB Step 1/10 : FROM golang:1.13 ---\u0026gt; 3a7408f53f79 Step 2/10 : COPY . ./goapp ---\u0026gt; Using cache ---\u0026gt; 2ce3dd5f2452 Step 3/10 : WORKDIR ./goapp ---\u0026gt; Using cache ---\u0026gt; 6876fbc22a61 Step 4/10 : RUN go mod download \u0026amp;\u0026amp; CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /app . ---\u0026gt; Using cache ---\u0026gt; 3bc411f60c04 Step 5/10 : FROM scratch ---\u0026gt; Step 6/10 : LABEL maintainer=\u0026#34;usimihsr\u0026#34; ---\u0026gt; Running in ca17656408ee Removing intermediate container ca17656408ee ---\u0026gt; a27e151d506a Step 7/10 : WORKDIR goapp ---\u0026gt; Running in 6f5f0ef76f49 Removing intermediate container 6f5f0ef76f49 ---\u0026gt; 19dcf5fdbbb4 Step 8/10 : COPY --from=0 /app ./ ---\u0026gt; 76bbcf3bc727 Step 9/10 : COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt ---\u0026gt; 0f5a0c01f2c0 Step 10/10 : ENTRYPOINT [\u0026#34;./app\u0026#34;] ---\u0026gt; Running in fdaa63f93c93 Removing intermediate container fdaa63f93c93 ---\u0026gt; eafc764f5a6e Successfully built eafc764f5a6e Successfully tagged wikipedia-search:scratch $ docker container run wikipedia-search:scratch -srlimit=5 \u0026#39;イチロー\u0026#39; --------------------------------------------------- イチロー https://ja.wikipedia.org/?curid=1432262 --------------------------------------------------- 首位打者 (日本プロ野球) https://ja.wikipedia.org/?curid=38085 --------------------------------------------------- 国道262号 https://ja.wikipedia.org/?curid=126147 --------------------------------------------------- 河上イチロー https://ja.wikipedia.org/?curid=3682529 --------------------------------------------------- 新井宏昌 https://ja.wikipedia.org/?curid=688515 --------------------------------------------------- これまでと同じように動かせた.\nさらにイメージも軽くなった(13.5MB -\u0026gt; 7.52MB).\n$ docker image ls wikipedia-search REPOSITORY TAG IMAGE ID CREATED SIZE wikipedia-search scratch eafc764f5a6e 9 seconds ago 7.52MB wikipedia-search alpine b9635a037fe6 35 seconds ago 13.5MB wikipedia-search golang f75198355e97 About an hour ago 810MB やったぜ.\nGoのアプリを軽いDocker imageにすることができた.\n作ったイメージはここ.\nhttps://hub.docker.com/r/uzimihsr/wikipedia-search/tags\nおまけ ","date":"2020-03-15T13:36:21+09:00","image":"/post/2020-03-15-golang-build-image/2020-03-15-sotochan.jpg","permalink":"/post/2020-03-15-golang-build-image/","title":"GoアプリをDockerのscratchイメージで動かす"},{"content":"久しぶりのGo 最近k8sの勉強ばっかで開発っぽいことをやってなかったので, Go(golang)で遊んでみた.\nやったことのまとめ WikipediaのAPI(Wikimedia API)を叩いてみた Go(golang)でAPIクライアントもどきを作った 作ったクライアントはこんなかんじ.\nWikipediaの記事を検索した結果とそのURLが表示できる.\n$ wikipedia -srlimit=5 -lang=en \u0026#39;cat\u0026#39; --------------------------------------------------- Cat https://en.wikipedia.org/?curid=6678 --------------------------------------------------- Cat (disambiguation) https://en.wikipedia.org/?curid=434590 --------------------------------------------------- .cat https://en.wikipedia.org/?curid=1978706 --------------------------------------------------- Bengal cat https://en.wikipedia.org/?curid=63064 --------------------------------------------------- Cat Stevens https://en.wikipedia.org/?curid=78747 --------------------------------------------------- つかうもの MacBook Pro (Retina, 15-inch, Mid 2015) macOS Mojave 10.14 Go(golang) go version go1.13 darwin/amd64 Wikipedia API やったこと WikipediaAPIをたたく GoでAPIをたたく WikipediaAPIをたたく APIのページによると,\nWikipedia(日本語版)APIのURLは\nhttps://ja.wikipedia.org/w/api.php\nとなっている.\n今回は記事検索APIを使ってみる.\n記事検索をする場合はクエリパラメータに\naction=query, list=search, srsearch=\u0026lt;検索したい文字列\u0026gt;を指定して\nGETすればいいみたい.\nレスポンスボディの形式はformat=json(JSONの場合)でできるっぽい.\n例: \u0026ldquo;猫\u0026quot;で検索する場合\nGET http://ja.wikipedia.org/w/api.php?format=json\u0026action=query\u0026list=search\u0026srsearch=猫\n試しにcurlで叩いてみる.\nデフォルトでは検索結果の上位10件が返ってくる.\nAPIでの検索結果 # なんかリダイレクトされるみたいなのでLオプションは必須 # JSONはjqでいい感じに整形する $ curl -sSL \u0026#39;http://ja.wikipedia.org/w/api.php?action=query\u0026amp;format=json\u0026amp;list=search\u0026amp;srsearch=猫\u0026#39; | jq { \u0026#34;batchcomplete\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;continue\u0026#34;: { \u0026#34;sroffset\u0026#34;: 10, \u0026#34;continue\u0026#34;: \u0026#34;-||\u0026#34; }, \u0026#34;query\u0026#34;: { \u0026#34;searchinfo\u0026#34;: { \u0026#34;totalhits\u0026#34;: 26227 }, \u0026#34;search\u0026#34;: [ { \u0026#34;ns\u0026#34;: 0, \u0026#34;title\u0026#34;: \u0026#34;ネコ\u0026#34;, \u0026#34;pageid\u0026#34;: 1215264, \u0026#34;size\u0026#34;: 123050, \u0026#34;wordcount\u0026#34;: 16730, \u0026#34;snippet\u0026#34;: \u0026#34;黒\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt; - 全身の毛が黒色の\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;。 白猫 - 全身の毛が白色の\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;。 トラネコ（タビー） - トラのような縞模様がある\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;。茶トラ\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;、キジ\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;、サバ\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;など。 三毛\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt; - 3色（一般的に白・茶色・黒）の\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;。 錆び\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt; - 黒と茶色の2色の\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;。 はちわれ - 顔面が鼻筋を境にした八の字形の2色になっている\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;。\u0026#34;, \u0026#34;timestamp\u0026#34;: \u0026#34;2020-03-03T14:46:03Z\u0026#34; }, { \u0026#34;ns\u0026#34;: 0, \u0026#34;title\u0026#34;: \u0026#34;三毛猫ホームズシリーズ\u0026#34;, \u0026#34;pageid\u0026#34;: 227387, \u0026#34;size\u0026#34;: 55409, \u0026#34;wordcount\u0026#34;: 7737, \u0026#34;snippet\u0026#34;: \u0026#34;収録作品　三毛猫ホームズの運動会・三毛\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;ホームズのスクープ・三毛\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;ホームズのバカンス・三毛\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;ホームズの温泉旅行・三毛\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;ホームズの殺人展覧会・三毛\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;ホームズのバースデー・パーティ （9）三毛\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;ホームズのびっくり箱 収録作品　三毛\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;ホームズのびっくり箱・三毛\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;ホームズの名演奏・三毛\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;ホームズのパニック・三毛\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;ホームズの幽霊退治・三毛\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;\u0026#34;, \u0026#34;timestamp\u0026#34;: \u0026#34;2020-02-20T12:43:57Z\u0026#34; }, { \u0026#34;ns\u0026#34;: 0, \u0026#34;title\u0026#34;: \u0026#34;1905年\u0026#34;, \u0026#34;pageid\u0026#34;: 2506, \u0026#34;size\u0026#34;: 26827, \u0026#34;wordcount\u0026#34;: 3310, \u0026#34;snippet\u0026#34;: \u0026#34;が、1962年からは公式な場では使用されていない。 1月1日 - 日露戦争：旅順開城 1月1日 - 夏目漱石が『ホトトギス』1月号で、処女作『吾輩は\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;である』を連載開始 1月22日 - サンクトペテルブルクで血の日曜日事件発生 1月23日 - 奈良県鷲家口（現・吉野郡東吉野村）でニホンオオカミの捕\u0026#34;, \u0026#34;timestamp\u0026#34;: \u0026#34;2020-01-23T07:22:49Z\u0026#34; }, { \u0026#34;ns\u0026#34;: 0, \u0026#34;title\u0026#34;: \u0026#34;吾輩は猫である\u0026#34;, \u0026#34;pageid\u0026#34;: 13241, \u0026#34;size\u0026#34;: 32502, \u0026#34;wordcount\u0026#34;: 4469, \u0026#34;snippet\u0026#34;: \u0026#34;『吾輩は\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;である』（わがはいはねこである）は、夏目漱石の長編小説であり、処女小説である。1905年（明治38年）1月、『ホトトギス』に発表され、好評を博したため、翌1906年（明治39年）8月まで継続した。 「吾輩は\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;である。名前はまだ無い。どこで生れたかとんと見当がつかぬ。」という書き出しで始ま\u0026#34;, \u0026#34;timestamp\u0026#34;: \u0026#34;2020-02-03T23:11:05Z\u0026#34; }, { \u0026#34;ns\u0026#34;: 0, \u0026#34;title\u0026#34;: \u0026#34;猫騙し\u0026#34;, \u0026#34;pageid\u0026#34;: 229647, \u0026#34;size\u0026#34;: 3049, \u0026#34;wordcount\u0026#34;: 432, \u0026#34;snippet\u0026#34;: \u0026#34;\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;騙し（ねこだまし）とは相撲の戦法の一種である。 立合いと同時に相手力士の目の前に両手を突き出して掌を合わせて叩くもので、相手の目をつぶらせることを目的とする奇襲戦法の一つ。相手に隙を作り、有利な体勢を作るために使われる。普通の立合いではかなわないような、はるかに強い相手に対する一発勝負に使われる\u0026#34;, \u0026#34;timestamp\u0026#34;: \u0026#34;2019-07-18T01:55:22Z\u0026#34; }, { \u0026#34;ns\u0026#34;: 0, \u0026#34;title\u0026#34;: \u0026#34;クイズRPG 魔法使いと黒猫のウィズ\u0026#34;, \u0026#34;pageid\u0026#34;: 2969296, \u0026#34;size\u0026#34;: 32428, \u0026#34;wordcount\u0026#34;: 4747, \u0026#34;snippet\u0026#34;: \u0026#34;『クイズRPG 魔法使いと黒\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;のウィズ』（クイズRPG まほうつかいとくろねこのウィズ）は、2013年にコロプラで配信を開始したソーシャルゲーム。 2013年3月にAndroid版が、4月22日にiOS版が配信開始された。 2013年8月20日に英語版を日本・韓国・中国以外の全世界で、韓国語版を韓国で、それぞれGoogle\u0026#34;, \u0026#34;timestamp\u0026#34;: \u0026#34;2020-03-03T09:36:04Z\u0026#34; }, { \u0026#34;ns\u0026#34;: 0, \u0026#34;title\u0026#34;: \u0026#34;長靴猫シリーズ\u0026#34;, \u0026#34;pageid\u0026#34;: 2623926, \u0026#34;size\u0026#34;: 26739, \u0026#34;wordcount\u0026#34;: 3221, \u0026#34;snippet\u0026#34;: \u0026#34;『長靴\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;シリーズ』（ながぐつねこシリーズ）は、ペローの童話『長靴をはいた\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;』を原作とする、東映動画（現：東映アニメーション）製作による劇場版長編アニメーション映画シリーズの通称。『長靴をはいた\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;』（1969年）、『ながぐつ三銃士』（1972年）、『長靴をはいた\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;\u0026#34;, \u0026#34;timestamp\u0026#34;: \u0026#34;2020-02-20T12:09:42Z\u0026#34; }, { \u0026#34;ns\u0026#34;: 0, \u0026#34;title\u0026#34;: \u0026#34;三味線\u0026#34;, \u0026#34;pageid\u0026#34;: 19199, \u0026#34;size\u0026#34;: 17244, \u0026#34;wordcount\u0026#34;: 2591, \u0026#34;snippet\u0026#34;: \u0026#34;三味線（しゃみせん）は、日本の有棹弦楽器。もっぱら弾(はじ)いて演奏される撥弦楽器である。四角状の扁平な木製の胴の両面に\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;や犬の皮を張り、胴を貫通して伸びる棹に張られた弦を、通常、銀杏形の撥（ばち）で弾き演奏する。 成立は15世紀から16世紀にかけてとされ、戦国時代に琉球（現在の沖縄県）から伝来し\u0026#34;, \u0026#34;timestamp\u0026#34;: \u0026#34;2020-02-26T01:02:18Z\u0026#34; }, { \u0026#34;ns\u0026#34;: 0, \u0026#34;title\u0026#34;: \u0026#34;グーグーだって猫である\u0026#34;, \u0026#34;pageid\u0026#34;: 1181328, \u0026#34;size\u0026#34;: 17396, \u0026#34;wordcount\u0026#34;: 1792, \u0026#34;snippet\u0026#34;: \u0026#34;『グーグーだって\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;である』（グーグーだってねこである）は大島弓子の漫画作品、およびそれを原作とした映画作品およびテレビドラマ作品。 タイトル・ロールとなっているアメリカンショートヘアの\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;、「グーグー」を始めとする\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;たちと作者との生活を綴ったエッセイ漫画。『ヤングロゼ』1996年11月号から1997\u0026#34;, \u0026#34;timestamp\u0026#34;: \u0026#34;2020-02-26T13:44:59Z\u0026#34; }, { \u0026#34;ns\u0026#34;: 0, \u0026#34;title\u0026#34;: \u0026#34;迷い猫オーバーラン!\u0026#34;, \u0026#34;pageid\u0026#34;: 1727982, \u0026#34;size\u0026#34;: 70796, \u0026#34;wordcount\u0026#34;: 9877, \u0026#34;snippet\u0026#34;: \u0026#34;PJ ライトノベル ポータル 文学 『迷い\u0026lt;span class=\\\u0026#34;searchmatch\\\u0026#34;\u0026gt;猫\u0026lt;/span\u0026gt;オーバーラン！』（まよいねこオーバーラン！）は、松智洋による日本のライトノベル。 集英社スーパーダッシュ文庫より、2008年10月から全12巻が刊行されている。イラストは9巻までぺこが担当していたが、10巻はヤス、11巻は氷川へきる、最終12巻はみつみ美\u0026#34;, \u0026#34;timestamp\u0026#34;: \u0026#34;2020-01-25T04:22:18Z\u0026#34; } ] } } WikipediaのUIで検索した場合と同じような結果が得られることがわかる.\nいい感じ.\nこれでAPIの動作確認は完了.\nGoでAPIをたたく このままシェルスクリプト化しても便利だと思うんだけど,\n今回はGoの練習としてAPIクライアントっぽく作ってみる.\nmain.go package main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;flag\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;io/ioutil\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;net/url\u0026#34; \u0026#34;os\u0026#34; ) // JSONをパースするための構造体を定義 type WikipediaResponse struct { Query Query `json:\u0026#34;query\u0026#34;` } type Query struct { SearchInfo SearchInfo `json:\u0026#34;searchinfo\u0026#34;` Search []Search `json:\u0026#34;search\u0026#34;` } type SearchInfo struct { Totalhits int `json:\u0026#34;totalhits\u0026#34;` } type Search struct { Title string `json:\u0026#34;title\u0026#34;` PageId int `json:\u0026#34;pageid\u0026#34;` // Snippet string `json:\u0026#34;snippet\u0026#34;` } func main() { // 引数チェック var language = flag.String(\u0026#34;lang\u0026#34;, \u0026#34;ja\u0026#34;, \u0026#34;検索するwikiの言語. default: ja\u0026#34;) var srlimit = flag.Int(\u0026#34;srlimit\u0026#34;, 10, \u0026#34;検索件数. default: 10\u0026#34;) flag.Parse() if flag.NArg() != 1 { fmt.Println(\u0026#34;検索ワードを指定してください\u0026#34;) os.Exit(1) } if flag.NFlag() \u0026gt; 2 { fmt.Println(\u0026#34;言語以外のフラグは無効です\u0026#34;) os.Exit(1) } arg := flag.Arg(0) // APIを叩くためのURL作成 baseUrl := url.URL{} baseUrl.Scheme = \u0026#34;http\u0026#34; baseUrl.Host = fmt.Sprintf(\u0026#34;%s.wikipedia.org\u0026#34;, *language) baseUrl.Path = \u0026#34;w/api.php\u0026#34; query := baseUrl.Query() query.Set(\u0026#34;action\u0026#34;, \u0026#34;query\u0026#34;) query.Set(\u0026#34;list\u0026#34;, \u0026#34;search\u0026#34;) query.Set(\u0026#34;srsearch\u0026#34;, arg) query.Set(\u0026#34;srlimit\u0026#34;, fmt.Sprintf(\u0026#34;%d\u0026#34;, *srlimit)) query.Set(\u0026#34;format\u0026#34;, \u0026#34;json\u0026#34;) baseUrl.RawQuery = query.Encode() // 記事検索APIを叩く resp, err := http.Get(baseUrl.String()) if err != nil { log.Fatal(err) } defer resp.Body.Close() // レスポンスをパースする body, err := ioutil.ReadAll(resp.Body) wikipediaResponse := new(WikipediaResponse) err = json.Unmarshal(body, wikipediaResponse) // ヒットした記事が0件の場合は終了 if wikipediaResponse.Query.SearchInfo.Totalhits \u0026lt;= 0 { fmt.Println(\u0026#34;記事が見つかりませんでした。\u0026#34;) os.Exit(1) } // 記事タイトルとURLを表示 for _, v := range wikipediaResponse.Query.Search { fmt.Println(\u0026#34;---------------------------------------------------\u0026#34;) fmt.Println(v.Title) fmt.Printf(\u0026#34;https://%s.wikipedia.org/?curid=%d\\n\u0026#34;, *language, v.PageId) } fmt.Println(\u0026#34;---------------------------------------------------\u0026#34;) } やったこととしては\n引数でのクエリパラメータとオプションの受け付け リクエストURLの組み立てとAPIへのHTTPリクエストの実行 レスポンス(JSON)のパース 必要な情報の表示 だけ.\n特に難しかったのはAPIを叩く部分とレスポンスのパース部分.\nAPIを叩く部分についてはnet/urlを使ってURLを組み立てて,\nnet/httpを使ってリクエストを投げるようにした.\n基本的にはcurlで叩いたときと同じリクエストを送るようにした.\n公式パッケージencoding/jsonを使ったJSONのパースは結構面倒で,\n事前にJSONの構造を構造体として定義してやる必要がある.\nAPIのレスポンスはJSONが結構入れ子になっているので書くのが大変だった.\nまた, 今回はレスポンスのJSONから記事タイトルとidだけ抜き出して,\nhttps://ja.wikipedia.org/?curid=1215264 の形式にすることでページへのリンクを作成するようにした.\n実際に使ってみるとこんな感じ.\n# ビルドする $ ls go.mod main.go $ go build -o $GOPATH/bin/wikipedia . # GOPATHがPATHに入っていればwikipediaコマンドを呼び出せる $ wikipedia \u0026#39;猫\u0026#39; --------------------------------------------------- ネコ https://ja.wikipedia.org/?curid=1215264 --------------------------------------------------- 三毛猫ホームズシリーズ https://ja.wikipedia.org/?curid=227387 --------------------------------------------------- 1905年 https://ja.wikipedia.org/?curid=2506 --------------------------------------------------- 吾輩は猫である https://ja.wikipedia.org/?curid=13241 --------------------------------------------------- 猫騙し https://ja.wikipedia.org/?curid=229647 --------------------------------------------------- クイズRPG 魔法使いと黒猫のウィズ https://ja.wikipedia.org/?curid=2969296 --------------------------------------------------- 長靴猫シリーズ https://ja.wikipedia.org/?curid=2623926 --------------------------------------------------- 三味線 https://ja.wikipedia.org/?curid=19199 --------------------------------------------------- グーグーだって猫である https://ja.wikipedia.org/?curid=1181328 --------------------------------------------------- 迷い猫オーバーラン! https://ja.wikipedia.org/?curid=1727982 --------------------------------------------------- # 実行時引数でオプションを変えられる $ wikipedia -lang=en -srlimit=3 \u0026#39;イチロー\u0026#39; --------------------------------------------------- Ichirō https://en.wikipedia.org/?curid=1067866 --------------------------------------------------- Orix Buffaloes https://en.wikipedia.org/?curid=1145207 --------------------------------------------------- Ichiro Suzuki https://en.wikipedia.org/?curid=66417 --------------------------------------------------- なんかそれっぽいのが作れた.\nやったぜ.\nつくったやつはここ.\nhttps://github.com/uzimihsr/wikipedia-search\nAPIクライアントはこんな感じで一度作ってしまえば他にもいろいろできそう.\n時間があれば他のAPIにも対応させてwikipedia用のコマンドラインツールみたいなのを作っても面白いかもしれない(需要はなさそう).\nおまけ ","date":"2020-03-09T21:29:36+09:00","image":"/post/2020-03-09-golang-api-client/2020-03-09-sotochan.jpg","permalink":"/post/2020-03-09-golang-api-client/","title":"GoでWikipediaのAPIを叩いて記事検索した"},{"content":"PodとかDeploymentとか Kubernetes完全ガイドの続き.\nPodとかの話.\n読んだもの Kubernetes完全ガイド 5章(Workloadsリソース) 重要そうなところとかよく使いそうなところだけまとめる.\n読んだことのまとめ Pod ReplicaSet Deployment DaemonSet StatefulSet Job CronJob Pod Kubernetesの最小単位となるリソース.\nコンテナの起動を担当する.\nよく使いそうな設定項目 説明 .spec.containers[].name 任意のコンテナ名 .spec.containers[].image 使用するイメージ .spec.containers[].command イメージのENTRYPOINTを上書きできる .spec.containers[].args イメージのCMDを上書きできる .spec.dnsPolicy クラスタ外のDNSを利用する場合のみ\n\u0026quot;None\u0026quot;を指定する .spec.dnsConfig .spec.dnsPolicyが\u0026quot;None\u0026quot;の場合の詳細な設定\n(/etc/resolv.confに相当) .spec.hostAliases[] 全コンテナの/etc/hostsを書き換える 1つのPodには複数のコンテナを内包することができ, それらは同じIPを共有する.\nそれぞれのコンテナにはポート番号を変えてアクセスできる.\nまた, Pod内のコンテナに入って直接操作することもできる.\n# sample-2podはnginxとredisのコンテナを持つPod # 2つのコンテナは同じIP(10.4.2.6)を共有している $ kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES sample-2pod 2/2 Running 0 94s 10.4.2.6 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-0dgf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; # 確認のためポート転送する # localhost:8080 -\u0026gt; sample-2pod:80(nginxが使用) # localhost:8080 -\u0026gt; sample-2pod:6379(redisが使用) $ kubectl port-forward sample-2pod 8080:80 8081:6379 Forwarding from 127.0.0.1:8080 -\u0026gt; 80 Forwarding from [::1]:8080 -\u0026gt; 80 Forwarding from 127.0.0.1:8081 -\u0026gt; 6379 Forwarding from [::1]:8081 -\u0026gt; 6379 # 以下の確認が終わり次第Ctrl+Cで終了 # nginxへの疎通を確認 $ curl localhost:8080 \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;Welcome to nginx!\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1\u0026gt;Welcome to nginx!\u0026lt;/h1\u0026gt; \u0026lt;p\u0026gt;If you see this page, the nginx web server is successfully installed and working. Further configuration is required.\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;For online documentation and support please refer to \u0026lt;a href=\u0026#34;http://nginx.org/\u0026#34;\u0026gt;nginx.org\u0026lt;/a\u0026gt;.\u0026lt;br/\u0026gt; Commercial support is available at \u0026lt;a href=\u0026#34;http://nginx.com/\u0026#34;\u0026gt;nginx.com\u0026lt;/a\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;\u0026lt;em\u0026gt;Thank you for using nginx.\u0026lt;/em\u0026gt;\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; # redisへの疎通を確認 $ curl -s telnet://localhost:8081 set key01 value01 +OK get key01 $7 value01 quit +OK # Pod内のnginxコンテナに入って環境変数を表示 $ kubectl exec -it sample-2pod -c nginx-container -- /bin/sh -c \u0026#34;printenv\u0026#34; KUBERNETES_SERVICE_PORT=443 KUBERNETES_PORT=tcp://10.7.240.1:443 HOSTNAME=sample-2pod HOME=/root TERM=xterm KUBERNETES_PORT_443_TCP_ADDR=10.7.240.1 NGINX_VERSION=1.12.2-1~stretch PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin KUBERNETES_PORT_443_TCP_PORT=443 NJS_VERSION=1.12.2.0.1.14-1~stretch KUBERNETES_PORT_443_TCP_PROTO=tcp KUBERNETES_SERVICE_PORT_HTTPS=443 KUBERNETES_PORT_443_TCP=tcp://10.7.240.1:443 KUBERNETES_SERVICE_HOST=10.7.240.1 PWD=/ sample-2pod.yaml\nコンテナで使用するDocker imageのENTRYPOINTとCMDはPod定義で上書きできる.\n# .spec.containers[].commandと.spec.containers[].argsを設定したPod # Dockerfile: CMD:[\u0026#34;nginx\u0026#34;, \u0026#34;-g\u0026#34;, \u0026#34;daemon off;\u0026#34;] # 上書きした内容: command:[\u0026#34;/bin/sleep\u0026#34;] args:[\u0026#34;3600\u0026#34;] # この場合はnginxが立ち上がらずに3600秒sleepする $ kubectl get pods NAME READY STATUS RESTARTS AGE sample-entrypoint 1/1 Running 0 45s # ポート転送を行った状態(localhost:8080 -\u0026gt; sample-entrypoint:80)でアクセスしてもnginxが起動していないため何も返らない $ curl localhost:8080 curl: (52) Empty reply from server sample-entrypoint.yaml\nDockerfile(nginx)\nPod内の全コンテナの/etc/resolv.conf, /etc/hostsもPod定義で上書きできる.\n# .spec.dnsConfigを設定したPod $ kubectl exec -it sample-externaldns cat /etc/resolv.conf nameserver 8.8.8.8 nameserver 8.8.4.4 search example.com options ndots:5 # .spec.hostAliasesを設定したPod $ kubectl exec -it sample-hostaliases cat /etc/hosts # Kubernetes-managed hosts file. 127.0.0.1\tlocalhost ::1\tlocalhost ip6-localhost ip6-loopback fe00::0\tip6-localnet fe00::0\tip6-mcastprefix fe00::1\tip6-allnodes fe00::2\tip6-allrouters 10.4.2.9\tsample-hostaliases # Entries added by HostAliases. 8.8.8.8\tgoogle-dns\tgoogle-public-dns sample-externaldns.yaml\nsample-hostaliases.yaml\nReplicaSet Podのレプリカ(複製)を指定した数だけ維持するリソース.\nよく使いそうな設定項目 説明 .spec.replicas 維持するPodの数 .spec.selector.matchLabels 維持するPodのラベル\n基本的に.spec.template.metadata.labelsと同じものを指定する .spec.template 維持するPodの定義 ReplicaSetは生成時に指定した数のPodを作成し, それらをラベルで管理する.\n指定したラベルを持つPodの数がReplicaSet定義で指定した数より少なくなったときは追加し(セルフヒーリング),\nそれよりも多くなった場合は削除することでPod数を維持する.\n# ReplicaSetが存在している状態 $ kubectl get replicasets -o wide NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR sample-rs 3 3 3 72s nginx-container nginx:1.12 app=sample-app # ReplicaSetによって管理されているPod $ kubectl get pods -l app=sample-app NAME READY STATUS RESTARTS AGE sample-rs-9vh82 1/1 Running 0 6s sample-rs-f8922 1/1 Running 0 6s sample-rs-ql2fq 1/1 Running 0 6s # Podを手動で削除してみる $ kubectl delete pod sample-rs-9vh82 pod \u0026#34;sample-rs-9vh82\u0026#34; deleted # Pod数が3より少なくなったためPodが追加される(セルフヒーリング) $ kubectl get pods -l app=sample-app NAME READY STATUS RESTARTS AGE sample-rs-8f7tm 0/1 ContainerCreating 0 1s sample-rs-9vh82 0/1 Terminating 0 28s sample-rs-f8922 1/1 Running 0 28s sample-rs-ql2fq 1/1 Running 0 28s $ kubectl get pods -l app=sample-app NAME READY STATUS RESTARTS AGE sample-rs-8f7tm 1/1 Running 0 3s sample-rs-9vh82 0/1 Terminating 0 30s sample-rs-f8922 1/1 Running 0 30s sample-rs-ql2fq 1/1 Running 0 30s $ kubectl get pods -l app=sample-app NAME READY STATUS RESTARTS AGE sample-rs-8f7tm 1/1 Running 0 9s sample-rs-f8922 1/1 Running 0 36s sample-rs-ql2fq 1/1 Running 0 36s # ReplicaSetの.spec.replicasを2に減らす $ kubectl scale rs sample-rs --replicas 2 # Pod数が2より多いので削除される $ kubectl get pods -l app=sample-app NAME READY STATUS RESTARTS AGE sample-rs-8f7tm 0/1 Terminating 0 9m41s sample-rs-f8922 1/1 Running 0 10m sample-rs-ql2fq 1/1 Running 0 10m $ kubectl get pods -l app=sample-app NAME READY STATUS RESTARTS AGE sample-rs-f8922 1/1 Running 0 10m sample-rs-ql2fq 1/1 Running 0 10m sample-rs.yaml\nDeployment 複数のReplicaSetを管理するリソース.\nPodとReplicaSetの機能を内包しているので基本的にコンテナを扱うときはDeploymentを使用する.\nよく使いそうな設定項目 説明 .spec.replicas ReplicaSetと同じ .spec.selector.matchLabels ReplicaSetと同じ .spec.template 維持するPodの定義 .spec.strategy.type ローリングアップデートを使用しない場合のみ\nRecreateを指定する .spec.strategy.rollingUpdate.maxUnavailable ローリングアップデート時に許容できる\nPodの不足数 .spec.strategy.rollingUpdate.maxSurge ローリングアップデート時に許容できる\nPodの超過数 Podの更新を行う際はDeployment定義で指定した数に対して\n許容できるPodの不足数~超過数の範囲でPodの追加と削除を行う(ローリングアップデート).\nこれにより常にいくつかのPodが稼働しつづけるため,\n更新時にダウンタイムが発生しないメリットがある.\nこのとき, 新しいPodは同じタイミングで作成される新しいReplicaSetによって管理される.\n# Deploymentが存在している状態 $ kubectl get deployment -o wide kubectl get deployment -o wide NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR sample-deployment 3/3 3 3 11m nginx-container nginx:1.12 app=sample-app $ kubectl get rs -o wide NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR sample-deployment-6c5948bf66 3 3 3 17s nginx-container nginx:1.12 app=sample-app,pod-template-hash=6c5948bf66 $ kubectl get pods -l app=sample-app NAME READY STATUS RESTARTS AGE sample-deployment-6c5948bf66-4tk5h 1/1 Running 0 29s sample-deployment-6c5948bf66-6stnf 1/1 Running 0 29s sample-deployment-6c5948bf66-72flm 1/1 Running 0 29s # コンテナイメージを更新する $ kubectl set image deployment sample-deployment nginx-container=nginx:1.13 deployment.extensions/sample-deployment image updated # Podが段階的に更新される(ローリングアップデート) # 新たにReplicaSet(7b4f67c7bc)が作成され, そこに新しいPodが作成される $ kubectl get rs --watch NAME DESIRED CURRENT READY AGE sample-deployment-6c5948bf66 3 3 3 46s sample-deployment-7b4f67c7bc 1 0 0 0s sample-deployment-7b4f67c7bc 1 0 0 0s sample-deployment-7b4f67c7bc 1 1 0 0s sample-deployment-7b4f67c7bc 1 1 1 2s sample-deployment-6c5948bf66 2 3 3 55s sample-deployment-7b4f67c7bc 2 1 1 2s sample-deployment-6c5948bf66 2 3 3 55s sample-deployment-6c5948bf66 2 2 2 55s sample-deployment-7b4f67c7bc 2 1 1 2s sample-deployment-7b4f67c7bc 2 2 1 2s sample-deployment-7b4f67c7bc 2 2 2 3s sample-deployment-6c5948bf66 1 2 2 56s sample-deployment-7b4f67c7bc 3 2 2 3s sample-deployment-6c5948bf66 1 2 2 56s sample-deployment-6c5948bf66 1 1 1 56s sample-deployment-7b4f67c7bc 3 2 2 4s sample-deployment-7b4f67c7bc 3 3 2 4s sample-deployment-7b4f67c7bc 3 3 3 5s sample-deployment-6c5948bf66 0 1 1 58s sample-deployment-6c5948bf66 0 1 1 58s sample-deployment-6c5948bf66 0 0 0 59s # 更新後 $ kubectl get rs -o wide NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR sample-deployment-6c5948bf66 0 0 0 108s nginx-container nginx:1.12 app=sample-app,pod-template-hash=6c5948bf66 sample-deployment-7b4f67c7bc 3 3 3 55s nginx-container nginx:1.13 app=sample-app,pod-template-hash=7b4f67c7bc # ReplicaSetと同様にスケーリングも可能 $ kubectl scale deployment sample-deployment --replicas 5 deployment.extensions/sample-deployment scaled $ kubectl get pods -l app=sample-app NAME READY STATUS RESTARTS AGE sample-deployment-7b4f67c7bc-28rq4 1/1 Running 0 12m sample-deployment-7b4f67c7bc-ctzmp 1/1 Running 0 19s sample-deployment-7b4f67c7bc-gmcvc 1/1 Running 0 12m sample-deployment-7b4f67c7bc-knx5z 1/1 Running 0 12m sample-deployment-7b4f67c7bc-vr5m4 1/1 Running 0 19s sample-deployment.yaml\nDaemonSet Podを各Nodeに1個ずつ作成, 管理するリソース.\n監視などに使用される.\nよく使いそうな設定項目 説明 .spec.selector.matchLabels ReplicaSetと同じ .spec.template 維持するPodの定義 .spec.updateStrategy.type ローリングアップデートを使用しない場合のみ\nOnDeleteを指定する .spec.updateStrategy.rollingUpdate.maxUnavailable ローリングアップデート時に許容できる\nPodの不足数 各NodeにPodを1つずつしか配置できないというルール以外,\nPodの管理方法はReplicaSetと同じ.\n# Nodeの確認 $ kubectl get nodes NAME STATUS ROLES AGE VERSION gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-0dgf Ready \u0026lt;none\u0026gt; 66d v1.13.11-gke.14 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-n2mf Ready \u0026lt;none\u0026gt; 66d v1.13.11-gke.14 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-wk64 Ready \u0026lt;none\u0026gt; 66d v1.13.11-gke.14 # DaemonSetがある状態でPodを確認するとPodが各Nodeに1個ずつ存在している $ kubectl get ds NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE sample-ds 3 3 3 3 3 \u0026lt;none\u0026gt; 4s $ kubectl get pods -o wide -l app=sample-app NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES sample-ds-5s6gx 1/1 Running 0 4m16s 10.4.1.10 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-wk64 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-ds-9qs6j 1/1 Running 0 4m16s 10.4.2.17 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-0dgf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-ds-srblh 1/1 Running 0 4m16s 10.4.0.19 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-n2mf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; # 試しにPodを削除する $ kubectl delete pod sample-ds-5s6gx pod \u0026#34;sample-ds-5s6gx\u0026#34; deleted # DaemonSetと同様にセルフヒーリングが起こる $ kubectl get pods -o wide -l app=sample-app --watch NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES sample-ds-5s6gx 1/1 Running 0 6m1s 10.4.1.10 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-wk64 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-ds-9qs6j 1/1 Running 0 6m1s 10.4.2.17 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-0dgf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-ds-srblh 1/1 Running 0 6m1s 10.4.0.19 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-n2mf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-ds-5s6gx 1/1 Terminating 0 6m14s 10.4.1.10 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-wk64 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-ds-5s6gx 0/1 Terminating 0 6m15s \u0026lt;none\u0026gt; gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-wk64 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-ds-5s6gx 0/1 Terminating 0 6m21s \u0026lt;none\u0026gt; gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-wk64 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-ds-5s6gx 0/1 Terminating 0 6m21s \u0026lt;none\u0026gt; gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-wk64 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-ds-bg2bp 0/1 Pending 0 0s \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-ds-bg2bp 0/1 Pending 0 0s \u0026lt;none\u0026gt; gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-wk64 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-ds-bg2bp 0/1 ContainerCreating 0 0s \u0026lt;none\u0026gt; gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-wk64 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-ds-bg2bp 1/1 Running 0 1s 10.4.1.11 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-wk64 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; $ kubectl get pods -o wide -l app=sample-app NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES sample-ds-9qs6j 1/1 Running 0 11m 10.4.2.17 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-0dgf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-ds-bg2bp 1/1 Running 0 4m50s 10.4.1.11 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-wk64 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-ds-srblh 1/1 Running 0 11m 10.4.0.19 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-n2mf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-ds.yaml\nStatefulSet 複数のPodに番号をつけて管理し, データの永続化を行うリソース.\nDBなどに使用される.\nよく使いそうな設定項目 説明 .spec.replicas ReplicaSetと同じ .spec.selector.matchLabels ReplicaSetと同じ .spec.template 維持するPodの定義 .spec.updateStrategy.type ローリングアップデートを使用しない場合のみOnDeleteを指定する .spec.updateStrategy.rollingUpdate.partition ローリングアップデート時に更新しないPodの数 .spec.volumeClaimTemplates[] 各Podに紐づくPersistentVolumeClaimの定義 データの永続化にはPersistentVolumeを使用する.\nPersistentVolumeはPodが無くなっても残り続け,\nセルフヒーリングによって同じ番号のPodが作成された場合は自動でマウントされる.\n# StatefulSetを作成 $ kubectl apply -f sample-statefulset.yaml statefulset.apps/sample-statefulset created # Podが順番に作成される $ kubectl get pods -o wide -l app=sample-app --watch NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES sample-statefulset-0 0/1 Pending 0 1s \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-statefulset-0 0/1 Pending 0 1s \u0026lt;none\u0026gt; gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-wk64 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-statefulset-0 0/1 ContainerCreating 0 1s \u0026lt;none\u0026gt; gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-wk64 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-statefulset-0 1/1 Running 0 12s 10.4.1.13 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-wk64 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-statefulset-1 0/1 Pending 0 0s \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-statefulset-1 0/1 Pending 0 0s \u0026lt;none\u0026gt; gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-0dgf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-statefulset-1 0/1 ContainerCreating 0 0s \u0026lt;none\u0026gt; gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-0dgf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-statefulset-1 1/1 Running 0 10s 10.4.2.19 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-0dgf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-statefulset-2 0/1 Pending 0 0s \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-statefulset-2 0/1 Pending 0 0s \u0026lt;none\u0026gt; gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-n2mf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-statefulset-2 0/1 ContainerCreating 0 0s \u0026lt;none\u0026gt; gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-n2mf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-statefulset-2 1/1 Running 0 11s 10.4.0.21 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-n2mf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; $ kubectl get pods -o wide -l app=sample-app NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES sample-statefulset-0 1/1 Running 0 5m58s 10.4.1.13 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-wk64 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-statefulset-1 1/1 Running 0 5m46s 10.4.2.19 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-0dgf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-statefulset-2 1/1 Running 0 5m36s 10.4.0.21 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-n2mf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; # 同時にPersistentVolume(永続化領域)が作成されている $ kubectl get persistentvolumeclaims NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE www-sample-statefulset-0 Bound pvc-c325de12-53e6-11ea-9451-42010a8a005a 1Gi RWO standard 8m36s www-sample-statefulset-1 Bound pvc-cba6642f-53e6-11ea-9451-42010a8a005a 1Gi RWO standard 8m21s www-sample-statefulset-2 Bound pvc-d3f4bbce-53e6-11ea-9451-42010a8a005a 1Gi RWO standard 8m7s $ kubectl get persistentvolumes NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pvc-c325de12-53e6-11ea-9451-42010a8a005a 1Gi RWO Delete Bound default/www-sample-statefulset-0 standard 8m48s pvc-cba6642f-53e6-11ea-9451-42010a8a005a 1Gi RWO Delete Bound default/www-sample-statefulset-1 standard 8m34s pvc-d3f4bbce-53e6-11ea-9451-42010a8a005a 1Gi RWO Delete Bound default/www-sample-statefulset-2 standard 8m20s # 試しにPodにマウント(/usr/share/nginx/html)されたPersistentVolumeにファイルを作成 $ kubectl exec -it sample-statefulset-1 touch /usr/share/nginx/html/hoge.txt $ kubectl exec -it sample-statefulset-0 ls /usr/share/nginx/html lost+found $ kubectl exec -it sample-statefulset-1 ls /usr/share/nginx/html hoge.txt lost+found $ kubectl exec -it sample-statefulset-2 ls /usr/share/nginx/html lost+found # Podを削除してみる $ kubectl delete pod sample-statefulset-1 pod \u0026#34;sample-statefulset-1\u0026#34; deleted # ReplicaSetと同様にセルフヒーリングが起こる $ kubectl get pods -o wide -l app=sample-app --watch NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES sample-statefulset-0 1/1 Running 0 15m 10.4.1.13 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-wk64 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-statefulset-1 1/1 Running 0 14m 10.4.2.19 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-0dgf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-statefulset-2 1/1 Running 0 14m 10.4.0.21 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-n2mf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-statefulset-1 1/1 Terminating 0 15m 10.4.2.19 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-0dgf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-statefulset-1 0/1 Terminating 0 15m 10.4.2.19 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-0dgf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-statefulset-1 0/1 Terminating 0 15m 10.4.2.19 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-0dgf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-statefulset-1 0/1 Terminating 0 15m 10.4.2.19 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-0dgf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-statefulset-1 0/1 Pending 0 0s \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-statefulset-1 0/1 Pending 0 0s \u0026lt;none\u0026gt; gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-0dgf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-statefulset-1 0/1 ContainerCreating 0 0s \u0026lt;none\u0026gt; gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-0dgf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-statefulset-1 1/1 Running 0 11s 10.4.2.20 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-0dgf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; # 新しく作成されたPodに同じPersistentVolumeがマウントされるのでファイルが残っている $ kubectl exec -it sample-statefulset-1 ls /usr/share/nginx/html hoge.txt lost+found # StatefulSetごとPodを全削除してみる $ kubectl delete statefulsets sample-statefulset statefulset.apps \u0026#34;sample-statefulset\u0026#34; deleted # StatefulSetが消えてもPersistentVolumeは残る $ kubectl get persistentvolumes NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pvc-c325de12-53e6-11ea-9451-42010a8a005a 1Gi RWO Delete Bound default/www-sample-statefulset-0 standard 22m pvc-cba6642f-53e6-11ea-9451-42010a8a005a 1Gi RWO Delete Bound default/www-sample-statefulset-1 standard 22m pvc-d3f4bbce-53e6-11ea-9451-42010a8a005a 1Gi RWO Delete Bound default/www-sample-statefulset-2 standard 22m sample-statefulset.yaml\nJob 使い切りのPodを作成するリソース.\nバッチ処理などに使用される.\nよく使いそうな設定項目 説明 .spec.completions Podが正常終了する回数の上限 .spec.parallelism 並列に動かすPodの数 .spec.backoffLimit Podが失敗する回数の上限 .spec.activeDeadlineSeconds Jobの制限時間 .spec.template 実行するPodの定義 .spec.template.restartPolicy Pod失敗時の挙動\nNever : 新たにPodを作成\nOnFailure : 同じPodを再実行 Podが正常終了する(終了ステータス0を返す)前提で使用する.\n指定した数のPodが正常終了した場合にJobが完了扱いとなる.\n失敗回数の上限や制限時間を超過した場合はJobが失敗となる.\n# 60秒sleepするJobを作成 $ kubectl apply -f sample-job.yaml job.batch/sample-job created # Podが起動し60秒後にCompletedとなる $ kubectl get pods --watch NAME READY STATUS RESTARTS AGE sample-job-7k6bs 0/1 Pending 0 0s sample-job-7k6bs 0/1 Pending 0 0s sample-job-7k6bs 0/1 ContainerCreating 0 0s sample-job-7k6bs 1/1 Running 0 13s sample-job-7k6bs 0/1 Completed 0 73s # 30秒sleepするJobを作成 # 合計10回成功するまでPodを2個並列で実行する設定 $ kubectl apply -f sample-paralleljob.yaml job.batch/sample-paralleljob created $ kubectl get pods --watch NAME READY STATUS RESTARTS AGE sample-paralleljob-npscc 0/1 Pending 0 0s sample-paralleljob-2vlfk 0/1 Pending 0 0s sample-paralleljob-npscc 0/1 ContainerCreating 0 0s sample-paralleljob-2vlfk 0/1 ContainerCreating 0 0s sample-paralleljob-npscc 1/1 Running 0 2s sample-paralleljob-2vlfk 1/1 Running 0 2s sample-paralleljob-2vlfk 0/1 Completed 0 33s sample-paralleljob-262xj 0/1 Pending 0 0s sample-paralleljob-262xj 0/1 ContainerCreating 0 0s sample-paralleljob-npscc 0/1 Completed 0 33s sample-paralleljob-p755p 0/1 Pending 0 0s sample-paralleljob-p755p 0/1 ContainerCreating 0 0s sample-paralleljob-p755p 1/1 Running 0 2s sample-paralleljob-262xj 1/1 Running 0 2s sample-paralleljob-p755p 0/1 Completed 0 32s sample-paralleljob-dk5fg 0/1 Pending 0 0s sample-paralleljob-dk5fg 0/1 ContainerCreating 0 0s sample-paralleljob-262xj 0/1 Completed 0 32s sample-paralleljob-jbmdm 0/1 Pending 0 0s sample-paralleljob-jbmdm 0/1 ContainerCreating 0 0s sample-paralleljob-jbmdm 1/1 Running 0 3s sample-paralleljob-dk5fg 1/1 Running 0 3s sample-paralleljob-jbmdm 0/1 Completed 0 33s sample-paralleljob-gnmsk 0/1 Pending 0 0s sample-paralleljob-dk5fg 0/1 Completed 0 33s sample-paralleljob-6qr2q 0/1 Pending 0 0s sample-paralleljob-gnmsk 0/1 ContainerCreating 0 0s sample-paralleljob-6qr2q 0/1 ContainerCreating 0 0s sample-paralleljob-6qr2q 1/1 Running 0 2s sample-paralleljob-gnmsk 1/1 Running 0 2s sample-paralleljob-6qr2q 0/1 Completed 0 33s sample-paralleljob-6hdkq 0/1 Pending 0 1s sample-paralleljob-gnmsk 0/1 Completed 0 33s sample-paralleljob-pp8pz 0/1 Pending 0 0s sample-paralleljob-6hdkq 0/1 ContainerCreating 0 1s sample-paralleljob-pp8pz 0/1 ContainerCreating 0 0s sample-paralleljob-pp8pz 1/1 Running 0 2s sample-paralleljob-6hdkq 1/1 Running 0 3s sample-paralleljob-6hdkq 0/1 Completed 0 33s sample-paralleljob-pp8pz 0/1 Completed 0 32s sample-job.yaml\nsample-paralleljob.yaml\nCronJob 設定されたスケジュールに基づいてJobを実行するリソース.\nよく使いそうな設定項目 説明 .spec.schedule Jobを実行するスケジュール .spec.concurrencyPolicy 実行中のJobが終わる前に\n次の実行タイミングになったときの挙動\nAllow : 次のJobを実行\nForbid : 次のJobを実行しない\nReplace : 実行中のJobを中止して次のJobを実行 .spec.startingDeadlineSeconds 開始時刻が.spec.scheduleより遅れる場合に許容できる秒数 .spec.successfulJobsHistoryLimit 成功したJobを保存する数の上限 .spec.failedJobsHistoryLimit 失敗したJobを保存する数の上限 .spec.suspend CronJobを停止するかどうかの設定\n(true/false) .spec.jobTemplate 実行するJobの定義 # CronJobを起動 $ kubectl apply -f sample-cronjob.yaml # .spec.scheduleに設定したタイミングでJobが実行される $ kubectl get jobs --watch NAME COMPLETIONS DURATION AGE sample-cronjob-1582722300 0/1 1s sample-cronjob-1582722300 0/1 1s 1s sample-cronjob-1582722300 0/1 43s 43s sample-cronjob-1582722360 0/1 0s sample-cronjob-1582722360 0/1 0s 0s sample-cronjob-1582722300 1/1 85s 85s sample-cronjob-1582722360 1/1 41s 41s sample-cronjob-1582722420 0/1 0s sample-cronjob-1582722420 0/1 0s 0s sample-cronjob-1582722420 0/1 42s 42s sample-cronjob-1582722480 0/1 1s sample-cronjob-1582722480 0/1 0s 1s sample-cronjob-1582722420 0/1 94s 94s sample-cronjob-1582722480 1/1 42s 43s sample-cronjob-1582722540 0/1 0s sample-cronjob-1582722540 0/1 0s 0s sample-cronjob-1582722540 1/1 41s 42s おまけ ","date":"2020-02-26T22:10:56+09:00","image":"/post/2020-02-26-kubernetes-guide-chap5/2020-02-26-sotochan.jpg","permalink":"/post/2020-02-26-kubernetes-guide-chap5/","title":"Kubernetes完全に理解したい 5章"},{"content":"リバースプロキシ 最近監視用にラズパイ上でいろいろ動かしてるけど, だんだんポート番号を覚えきれなくなってきたのでいい感じにリバースプロキシした.\nやったことのまとめ ラズパイにnginxを突っ込んでリバースプロキシにした リクエストパスを使った監視関係へのリバースプロキシの設定がちょっと面倒だった つかうもの Raspberry Pi 3 Model B+ OSはRaspbian(10.0) Prometheus version 2.15.2 インストール済み Grafana v6.5.2 インストール済み Alertmanager version 0.20.0 インストール済み nginx 今回入れる リバースプロキシとして使用 version 1.14.2 構成 Icons made by Smashicons from www.flaticon.com\n現在ラズパイ上のPrometheus等のUIにアクセスする場合, 192.168.3.200:9090 のように\n各アプリが起動しているポート番号を指定してアクセスしているが,\nアプリのポート番号なんてすぐ忘れちゃう(バカ)のでラズパイ側でnginxを立ててリバースプロキシをかけることにする.\nリバースプロキシは8080番ポートでリクエストを受けて, パス(/prometheus)によってアクセスするアプリを変えるようにする.\n(例: 192.168.3.200:8080/prometheus 宛のリクエストに 192.168.3.200:9090 で動くアプリのレスポンスを返す)\nポート番号よりはパスのほうが覚えやすいので便利になる. はず\u0026hellip;\nNode exporterはUIがないので今回は対象外.\nやったこと nginxのインストール リバースプロキシの設定 nginxのインストール nginxのインストール自体は簡単. aptで入れるだけ.\n# nginxのインストール $ sudo apt update $ sudo apt install nginx # service起動 $ sudo systemctl start nginx $ systemctl status nginx ● nginx.service - A high performance web server and a reverse proxy server Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled) Active: active (running) since Tue 2020-01-28 22:12:54 JST; 7min ago Docs: man:nginx(8) Process: 23808 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS) Process: 23809 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS) Main PID: 23810 (nginx) Tasks: 5 (limit: 2200) Memory: 3.5M CGroup: /system.slice/nginx.service ├─23810 nginx: master process /usr/sbin/nginx -g daemon on; master_process on; ├─23811 nginx: worker process ├─23812 nginx: worker process ├─23813 nginx: worker process └─23814 nginx: worker process Jan 28 22:12:54 raspberrypi systemd[1]: Starting A high performance web server and a reverse proxy server... Jan 28 22:12:54 raspberrypi systemd[1]: Started A high performance web server and a reverse proxy server. 試しに\u0026lt;ラズパイのIP\u0026gt;:80にアクセスするとnginxの初期画面のhtmlが表示される. かんたん.\nリバースプロキシの設定 このnginxをリバースプロキシとして使うための設定をする.\nデフォルトで作成されているnginxの設定ファイル/etc/nginx/nginx.confは\n/etc/nginx/conf.d/*.confを読み込むようになっているので,\n新たに/etc/nginx/conf.d/default.conf(名前は何でも良い)を作成する.\n今回は8080番ポートで受けてパス(location)により各アプリのポートに割り振るリバースプロキシを設定する.\n# nginxの設定ファイルを確認する # 編集はしない $ cat /etc/nginx/nginx.conf # リバースプロキシ設定を作成 $ sudo vim /etc/nginx/conf.d/default.conf nginx.conf(初期設定のまま) user www-data; worker_processes auto; pid /run/nginx.pid; include /etc/nginx/modules-enabled/*.conf; events { worker_connections 768; # multi_accept on; } http { ## # Basic Settings ## sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; # server_tokens off; # server_names_hash_bucket_size 64; # server_name_in_redirect off; include /etc/nginx/mime.types; default_type application/octet-stream; ## # SSL Settings ## ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE ssl_prefer_server_ciphers on; ## # Logging Settings ## access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; ## # Gzip Settings ## gzip on; # gzip_vary on; # gzip_proxied any; # gzip_comp_level 6; # gzip_buffers 16 8k; # gzip_http_version 1.1; # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; ## # Virtual Host Configs ## # ここでdefault.confを参照する include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; } default.conf server { listen\t8080; location /prometheus/ { proxy_pass\thttp://localhost:9090/; } location /alertmanager/ { proxy_pass\thttp://localhost:9093/; } location /grafana/ { proxy_pass\thttp://localhost:3000/; } } また, このリバースプロキシが効くように各アプリの外部URL(nginxが見るやつ)を変える必要がある.\nどれも起動時に読み込むオプションで指定できるので, 設定ファイルを編集する.\n# 各アプリのserviceファイルを修正する # Prometheus $ sudo vim /etc/systemd/system/prometheus.service # Alertmanager $ sudo vim /etc/systemd/system/alertmanager.service # Grafanaだけはパッケージインストールしたので別の設定ファイルを編集する $ sudo vim /etc/grafana/grafana.ini prometheus.service [Unit] Description=Prometheus Server [Service] User=prometheus ExecStart=/usr/local/prometheus/prometheus \\ --config.file=/usr/local/prometheus/prometheus.yml \\ --storage.tsdb.path=/var/lib/prometheus/data \\ --web.external-url=http://localhost:8080/prometheus/ \\ --web.route-prefix=/ [Install] WantedBy=multi-user.target alertmanager.service [Unit] Description=Alertmanager [Service] User=alertmanager ExecStart=/usr/local/alertmanager/alertmanager \\ --config.file=/usr/local/alertmanager/alertmanager.yml \\ --storage.path=/var/lib/alertmanager/data \\ --web.external-url=http://localhost:8080/alertmanager/ \\ --web.route-prefix=/ [Install] WantedBy=multi-user.target grafana.ini(一部抜粋) ... [server] # The public facing domain name used to access grafana from a browser domain = localhost ... # The full public facing url you use in browser, used for redirects and emails # If you use reverse proxy and sub path specify full url (with sub path) root_url = %(protocol)s://%(domain)s/grafana/ ... PrometheusとAlertmanagerの設定はほぼ同じ.\n重要なのはnginxから見える外部URLのフラグ--web.external-urlの設定で\n実際に起動している場所(localhost:9090, localhost:9093)とは違うパス(localhost:8080/prometheus, localhost:8080/alertmanager)からアクセスできるようにしていることと,\nPrometheus(Alertmanager)がリクエストを処理するルートパスのフラグ--web.route-prefixを / のみにすることで\nリバースプロキシを経由する場合としない場合で同じルートパスを扱えるようにしていることである.\n--web.route-prefixを指定しない例(Prometheus): --web.route-prefixはデフォルトだと--web.external-urlのパス(/prometheus/)と同じになってしまうため, リバースプロキシを経由しない場合のリクエストが処理できない. nginxを経由せずlocalhost内のアプリ同士で通信する場合などに不便. リクエスト 処理 localhost:8080/prometheus/metrics -\u0026gt; /metrics(リバースプロキシ) localhost:9090/metrics -\u0026gt; ルートパスが/prometheus/でないため解釈できずBad Request localhost:9090/prometheus/metrics -\u0026gt; /metrics(リバースプロキシなし), しかし不便 Grafanaの設定も似たようなことをやっていて,\nこちらは localhost:8080/grafana でアクセスできるようになっているが,\nリバースプロキシをかけない場合(localhost:3000)はエラーになる.\n(ただしGrafanaに人間以外がアクセスすることはほとんど無いのでPrometheusの場合と違って困ることはない)\nこのあたりはかなり難しかったので以下の記事を参考にした.\nhttps://www.robustperception.io/external-urls-and-path-prefixes\nhttps://grafana.com/docs/grafana/latest/installation/behind_proxy/\nここまで設定できたら, 各サービスを再起動する.\n# serviceファイルが書き換わっているのでリロード $ sudo systemctl daemon-reload # 全部まとめて再起動 $ sudo systemctl restart prometheus alertmanager grafana-server nginx 試しに\u0026lt;ラズパイのIP\u0026gt;:8080/prometheus, \u0026lt;ラズパイのIP\u0026gt;:8080/alertmanager, \u0026lt;ラズパイのIP\u0026gt;:8080/grafanaにそれぞれアクセスするとそれぞれのUIが開く.\nやったぜ.\nおわり これでリバースプロキシの8080番だけ覚えればあとは名前だけで各アプリにたどり着けるようになったのでとても便利.\nリバースプロキシの練習にもなったのでよかった.\nおまけ ","date":"2020-01-29T21:52:57+09:00","image":"/post/2020-01-29-nginx/2020-01-29-sotochan.jpg","permalink":"/post/2020-01-29-nginx/","title":"Prometheusとかをnginxでリバースプロキシした"},{"content":"やばい時には通知する 監視の練習として, Alertmanagerを使ってラズパイから通知をGmailで送るようにした.\nやったことのまとめ Googleアカウントの設定をしてSMTPサーバーを使えるようにした ラズパイにAlertmanagerを突っ込んでGmail経由で通知を送るようにした つかうもの Raspberry Pi 3 Model B+ OSはRaspbian(10.0) Node exporter version=\u0026ldquo;0.18.1\u0026rdquo; Prometheus version=\u0026ldquo;2.15.2\u0026rdquo; Alertmanager version=\u0026ldquo;0.20.0\u0026rdquo; 今回入れる Googleのアカウント Node exporterとPrometheusはラズパイにインストール済み\n構成 Icons made by Smashicons from www.flaticon.com 今回は図のような構成で通知を送る.\nPrometheus は各 exporter を常に監視しており,\n監視対象(ラズパイ)に何か問題が発生した場合 Node exporter のメトリクスが変化する.\nPrometheus では監視対象のメトリクスについて設定した条件(放っておいたらまずい状態)を満たした際にアラートを発火させる.\nAlertmanager は発火したアラートに応じた処理を行うが,\n今回はGmail経由で管理者に通知するようにする.\nやったこと SMTPサーバーの設定 Alertmanagerのインストール 通知設定とテスト SMTPサーバーの設定 まずはラズパイ上のアプリケーションからメールを送れるようにSMTPサーバーの設定を行う.\n今回はGmailをSMTPサーバーとして使用するが, 他に利用可能なメールサーバーがあれば飛ばしても良い.\nここを参考に, Googleアカウントでアプリ用パスワードを設定する.\nhttps://myaccount.google.com/u/1/security を開き, セキュリティ-\u0026gt;2段階認証プロセスと進む.\nもろもろの設定をして2段階認証を有効化する.\n細かい設定手順はここを参考にする.\n再度 https://myaccount.google.com/u/1/security を開くとアプリパスワードの項目が増えているので進む.\nアプリを選択からその他(名前を入力)を選び, 適当な名前(alertmanager)を入力して生成を押す.\n16文字のアプリパスワードが生成されるので覚えておく.\nこのパスワードを使ってラズパイからメールを送る.\nSMTPサーバーの設定はここを参考に後ほどAlertmanagerの設定に記述する.\n項目 値 SMTPホスト名 smtp.gmail.com ポート番号 587 アカウント アプリパスワードを発行したアカウント名 パスワード アプリパスワード 以上でGmailをSMTPサーバーとして使うための準備は完了.\nAlertmanagerのインストール 次にPrometheusで発火したアラートを処理するためのAlertmanagerをインストールする.\nファイルは前回と同じようにここからダウンロードする. Architectureはarmv7であることに注意.\n# 任意のディレクトリで実行 $ cd /path/to/workspace # Alertmanagerをダウンロードして展開, フォルダを移動 $ wget https://github.com/prometheus/alertmanager/releases/download/v0.20.0/alertmanager-0.20.0.linux-armv7.tar.gz $ tar -xzf alertmanager-0.20.0.linux-armv7.tar.gz $ sudo cp -a alertmanager-0.20.0.linux-armv7 /usr/local/alertmanager # service用ユーザの作成, データ保存用ディレクトリの作成 $ sudo useradd -U -s /sbin/nologin -M -d / alertmanager $ sudo mkdir -p /var/lib/alertmanager/data $ sudo chown -R alertmanager:alertmanager /var/lib/alertmanager # serviceファイルを作成 $ sudo vim /etc/systemd/system/alertmanager.service # serviceを起動 $ sudo systemctl daemon-reload $ sudo systemctl enable alertmanager Created symlink /etc/systemd/system/multi-user.target.wants/alertmanager.service → /etc/systemd/system/alertmanager.service. $ sudo systemctl start alertmanager $ systemctl status alertmanager ● alertmanager.service - Alertmanager Loaded: loaded (/etc/systemd/system/alertmanager.service; enabled; vendor preset: enabled) Active: active (running) since Mon 2020-01-27 00:10:38 JST; 4s ago Main PID: 9413 (alertmanager) Tasks: 13 (limit: 2200) Memory: 7.7M CGroup: /system.slice/alertmanager.service └─9413 /usr/local/alertmanager/alertmanager --config.file=/usr/local/alertmanager/alertmanager.yml --storage.path=/var/lib/alertmanager/data ... Jan 27 00:10:38 raspberrypi alertmanager[9413]: level=info ts=2020-01-26T15:10:38.829Z caller=main.go:497 msg=Listening address=:9093 ... alertmanager.service [Unit] Description=Alertmanager [Service] User=alertmanager ExecStart=/usr/local/alertmanager/alertmanager \\ --config.file=/usr/local/alertmanager/alertmanager.yml \\ --storage.path=/var/lib/alertmanager/data [Install] WantedBy=multi-user.target \u0026lt;ラズパイのIP\u0026gt;:9093をブラウザで開くとAlertmanagerのUI画面が表示される.\n(ラズパイのブラウザで開く場合はlocalhost:9093)\nこれでAlertmanagerのインストールと起動ができた.\n通知設定とテスト 次にPrometheusでアラートを発生させるための設定を行う.\nアラートルールの設定ファイルrules.ymlで監視対象がダウンしたときに発火するInstanceDownアラートを定義し,\nprometheus.ymlでこれを参照するよう設定する.\n# アラートの条件を記述するファイルを作成 $ vim /usr/local/prometheus/rules.yml # prometheusの設定ファイルを修正 $ vim /usr/local/prometheus/prometheus.yml # prometheusのserviceを再起動 $ sudo systemctl restart prometheus rules.yml groups: - name: instance rules: - alert: InstanceDown expr: up == 0 # 監視対象のupが0(down)になり for: 1m # かつその状態が1分以上続くと発火 prometheus.yml global: scrape_interval: 15s evaluation_interval: 15s rule_files: - rules.yml # アラート設定を読み込む alerting: alertmanagers: - static_configs: - targets: [\u0026#39;localhost:9093\u0026#39;] # 9093番ポートで起動しているAlertmanagerにアラートを送る scrape_configs: - job_name: \u0026#39;prometheus\u0026#39; static_configs: - targets: [\u0026#39;localhost:9090\u0026#39;] - job_name: \u0026#39;node\u0026#39; static_configs: - targets: [\u0026#39;localhost:9100\u0026#39;] この時点で\u0026lt;ラズパイのIP\u0026gt;:9090をブラウザで開き, Alertsを選択するとアラート設定が有効になっていることがわかる.\nここで試しに監視対象のNode exporterを止めてアラートを発火させてみる.\n# Node exporterのサービスを止める $ sudo systemctl stop node_exporter # アラートの動作確認(発火)が完了した後で再度サービスを起動する $ sudo systemctl start node_exporter rules.ymlで定義したとおり,\n監視対象(Node exporter)がダウンして1分経つとアラートが発火する.\nまた, PrometheusからAlertmanagerにアラートが飛んできていることが確認できる.\n以上でPrometheusのアラート設定は完了.\n次にAlertmanagerでアラートを処理する設定を行う.\nAlertmanagerの設定ファイルalertmanager.yamlを修正し,\nアラートが飛んできた際の処理を設定する.\nメール設定の部分にSMTPサーバーの設定で確認した項目を書き込む.\n設定の書き方はここを参考にする.\n# Alertmanagerの設定ファイルを編集 $ vim /usr/local/alertmanager/alertmanager.yml # サービスを再起動 $ sudo systemctl restart alertmanager alertmanager.yml global: resolve_timeout: 5m smtp_smarthost: \u0026#39;smtp.gmail.com:587\u0026#39; # SMTPサーバーのホスト名 smtp_from: \u0026#39;xxxxxxxxx@gmail.com\u0026#39; # 先程設定したアカウントのメールアドレスを送信元に設定 smtp_auth_username: \u0026#39;xxxxxxxxx\u0026#39; # アカウント名 smtp_auth_password: \u0026#39;wwwwwwwwwwwwwwww\u0026#39; # アプリパスワード route: group_by: [\u0026#39;alertname\u0026#39;] group_wait: 10s group_interval: 10s repeat_interval: 1h receiver: alert-email receivers: - name: alert-email email_configs: - to: \u0026#39;yyyyyyyyy@gmail.com\u0026#39; # 通知を受け取るメールアドレスを設定 以上でAlertmanagerからGmail経由で通知メールを送る設定は完了.\n動作確認のため, 先程と同様にNode exporterを再度落としてみる.\n# Node exporterのサービスを止める $ sudo systemctl stop node_exporter # アラートの動作確認(発火)が完了したら再度サービスを起動する $ sudo systemctl start node_exporter 宛先に指定したメールアドレス(yyyyyyyyy@gmail.com)の受信トレイを確認すると,\nアラートメールが送信されていることがわかる.\nやったぜ.\nこれでPrometheusのアラートをGmail経由で通知できるようになった.\nおわり 以上でラズパイ監視のアラートを通知できるようになった.\nいよいよ監視っぽくなってきた\u0026hellip;\nおまけ ","date":"2020-01-27T22:52:41+09:00","image":"/post/2020-01-27-alertmanager-gmail/2020-01-27-sotochan.jpg","permalink":"/post/2020-01-27-alertmanager-gmail/","title":"AlertmanagerからGmailを使って通知するようにした"},{"content":"パスワード打つのめんどい 家にあるラズパイにSSHできるようにしたはいいものの, 毎回パスワード入れるのも面倒だし真面目に考えたらセキュリティ的にもよろしくないので公開鍵認証で入るようにした.\nやったことのまとめ MacBookの公開鍵をラズパイ側に登録してログインできるようにした ラズパイ側でパスワードログインを無効化した つかうもの Raspberry Pi 3 Model B+ SSHサーバー(入られる側) OSはRaspbian(10.0) IPは固定済み MacBook SSHクライアント(入る側) opensshがあれば正直なんでもいい 構成 Icons made by Smashicons from www.flaticon.com 公開鍵認証のざっくりとしたおさらい\u0026hellip;\nMacBook(クライアント)の秘密鍵(id_rsa)と公開鍵(id_rsa.pub)を\nラズパイ(サーバー)の鍵リスト(authorized_keys)に登録する.\n秘密鍵と公開鍵は1つのペアになっていて, それぞれ片方からもう片方を作成できないようになっている.\nまた, 秘密鍵と公開鍵を照らし合わせるとペアになっているかどうかを判別できるようになっているので,\nMacBookからラズパイにSSHする際にはラズパイ側の鍵リストに事前に登録された公開鍵と\nMacBookが持つ秘密鍵を照らし合わせ, ペアと認められた場合にログインを許可する(認証).\nやったこと 公開鍵をラズパイ側に登録 パスワード認証を無効化 公開鍵をラズパイ側に登録 まずはクライアント(MacBook)側で秘密鍵と公開鍵のペアを作成する.\nすでに~/.ssh/id_rsa(秘密鍵)と~/.ssh/id_rsa.pub(公開鍵)がある場合は飛ばしていい.\n# MacBookで実行 # 秘密鍵と公開鍵のペアを作成する $ ssh-keygen $ ls ~/.ssh id_rsa id_rsa.pub known_hosts 次にMacBookの公開鍵をラズパイ側に登録する.\n# 全てMacBookで実行 # ラズパイに公開鍵を保存するためのファイル(~/.ssh/authorized_keys)を作成 # ここではまだパスワード認証 $ ssh pi@192.168.3.200 \u0026#34;touch ~/.ssh/authorized_keys; ls ~/.ssh\u0026#34; authorized_keys # このファイルにMacBookの公開鍵を書き込む id_rsa # これはラズパイの秘密鍵なので関係ない id_rsa.pub # 同じく関係なし known_hosts # 同じく関係なし # ラズパイのauthorized_keysにMacBookの公開鍵を書き込んで権限を変更 # まだパスワード認証 $ cat ~/.ssh/id_rsa.pub | ssh pi@192.168.3.200 \u0026#34;cat \u0026gt;\u0026gt; ~/.ssh/authorized_keys; chmod 700 ~/.ssh/authorized_keys\u0026#34; # 念の為鍵が登録されたか確認 $ cat ~/.ssh/id_rsa.pub ssh-rsa AAAB3...wlj uzimihsr@macbook.local # MacBookの公開鍵が登録されているのでパスワードを聞かれなくなっている $ ssh pi@192.168.3.200 \u0026#34;cat ~/.ssh/authorized_keys\u0026#34; ssh-rsa AAAB3...wlj uzimihsr@macbook.local やったぜ.\nこれでパスワードなしでもSSHできるようになった.\nパスワード認証を無効化 このままでもいいんだけど, セキュリティ的にパスワード認証はあまりよろしくない(総当りされたらクソ雑魚ナメクジ)ので,\nパスワード認証を無効化する.\nラズパイの/etc/ssh/sshd_configを編集してパスワード認証の可否を変更する.\n# 全てラズパイで実行 # SSHサーバー側の設定ファイルを編集 $ sudo vim /etc/ssh/sshd_config ... # To disable tunneled clear text passwords, change to no here! PasswordAuthentication no # ここをyesからnoに変更 #PermitEmptyPasswords no ... # 念の為sshd.serviceを再起動 $ sudo systemctl restart ssh.service ちゃんとパスワード認証が無効化されているか確認する.\n公開鍵認証ではクライアント(MacBook)の秘密鍵とサーバー(ラズパイ)に登録された公開鍵が対応していないと認証が通らないので,\nMacBook側の秘密鍵を使えなくするとログインできなくなる.\n# 全てMacBookで実行 # 秘密鍵の名前を変えて読めない状態にする $ mv ~/.ssh/id_rsa ~/.ssh/id_rsa.bkup # ラズパイにSSHを試みる # MacBookの秘密鍵が使えず, パスワード認証が禁止されているので弾かれる $ ssh pi@192.168.3.200 pi@192.168.3.200: Permission denied (publickey). # 秘密鍵の名前をもとに戻して使えるようにする $ mv ~/.ssh/id_rsa.bkup ~/.ssh/id_rsa # 再びラズパイにSSHを試みる # 今度は秘密鍵が使えるので問題なくログインできる $ ssh pi@192.168.3.200 ... Last login: Thu Jan 23 23:24:24 2020 from ... やったぜ.\nこれで鍵認証でしかログインできなくなった.\nおわり ラズパイでの作業がだいぶ楽になった.\nおまけ ","date":"2020-01-23T22:49:13+09:00","image":"/post/2020-01-23-raspberry-pi-ssh-key/2020-01-23-sotochan.jpg","permalink":"/post/2020-01-23-raspberry-pi-ssh-key/","title":"ラズパイに公開鍵でSSHするようにした"},{"content":"監視の練習 ラズパイを使ってPrometheusによる監視の練習をしてみた.\nやったことのまとめ ラズパイにNode exporterとPrometheusを入れてメトリクスを取れるようにした Grafanaも突っ込んでダッシュボードを作ってみた 全部サービス化して自動起動するようにした 実際につくったもの.\nラズパイのシステム情報がブラウザから簡単に確認できる.\nつかうもの Raspberry Pi 3 Model B+ OSはRaspbian(10.0) Node exporter version=\u0026ldquo;0.18.1\u0026rdquo; Prometheus version=\u0026ldquo;2.15.2\u0026rdquo; Grafana v6.5.2 構成 こんな感じの構成でラズパイのシステム情報を可視化する.\nNode exporter がマシンのシステム情報(CPU使用率など)を数値化したメトリクスを吐き出し続け,\nPrometheus でexporterの吐き出すメトリクスをモニタリングして時系列化.\n最後に Grafana が Prometheus の持つ時系列データを取り出して可視化する,\nという基本的な構成.\nGrafana で可視化したデータは同じネットワークのブラウザから見ることができる.\n今回はこれらのコンポーネントを1台のマシンにまとめてしまうが, 複数台のマシンに分散させることも可能である.\nやったこと Node exporterのインストール Prometheusのインストール Grafanaのインストール Node exporterのインストール まずはNode exporterをインストールする.\nブラウザでPrometheusのダウンロードページを開き, Architectureをarmv7にした状態でnode_exporter-0.18.1.linux-armv7.tar.gzをダウンロードする.\nもしくは, コマンドラインからwgetする.\n# 任意のディレクトリで実行 $ cd /path/to/workspace # node_exporterをダウンロードして展開, バイナリを移動 $ wget https://github.com/prometheus/node_exporter/releases/download/v0.18.1/node_exporter-0.18.1.linux-armv7.tar.gz $ tar -xzf node_exporter-0.18.1.linux-armv7.tar.gz $ sudo cp node_exporter-0.18.1.linux-armv7/node_exporter /usr/local/bin 動作確認してみる.\n# Node exporterを起動 $ /usr/local/bin/node_exporter INFO[0000] Starting node_exporter (version=0.18.1, branch=HEAD, revision=3db77732e925c08f675d7404a8c46466b2ece83e) source=\u0026#34;node_exporter.go:156\u0026#34; INFO[0000] Build context (go=go1.12.5, user=root@b50852a1acba, date=20190604-16:42:57) source=\u0026#34;node_exporter.go:157\u0026#34; INFO[0000] Enabled collectors: source=\u0026#34;node_exporter.go:97\u0026#34; INFO[0000] - arp source=\u0026#34;node_exporter.go:104\u0026#34; ... INFO[0000] - zfs source=\u0026#34;node_exporter.go:104\u0026#34; INFO[0000] Listening on :9100 source=\u0026#34;node_exporter.go:170\u0026#34; # 動作確認が終わり次第Ctrl+Cで終了する 9100番ポートで起動しているので, ラズパイか同じネットワークにあるマシンのブラウザから\u0026lt;ラズパイのIP\u0026gt;:9100/metricsを開くと, メトリクスの一覧が表示される.\n(画像にあるIPは前回固定したもの)\n毎回手動で起動するのは不便なのでサービス化しておく.\nこちらのサンプルの通りにやってみる.\n# service起動に必要なuser(node_exporter)を追加する $ sudo useradd -U -s /sbin/nologin -M -d / node_exporter # serviceファイルを作成する $ sudo vim /etc/systemd/system/node_exporter.service # serviceの起動 $ sudo systemctl daemon-reload $ sudo systemctl enable node_exporter Created symlink /etc/systemd/system/multi-user.target.wants/node_exporter.service → /etc/systemd/system/node_exporter.service. $ sudo systemctl start node_exporter $ systemctl status node_exporter ● node_exporter.service - Node Exporter Loaded: loaded (/etc/systemd/system/node_exporter.service; enabled; vendor preset: enabled) Active: active (running) since Wed 2020-01-15 22:50:41 JST; 3 days ago Main PID: 19378 (node_exporter) Tasks: 13 (limit: 2200) Memory: 17.8M CGroup: /system.slice/node_exporter.service └─19378 /usr/local/bin/node_exporter ... Jan 15 22:50:41 raspberrypi node_exporter[19378]: time=\u0026#34;2020-01-15T22:50:41+09:00\u0026#34; level=info msg=\u0026#34;Listening on :9100\u0026#34; source=\u0026#34;node_exporter.go:170\u0026#34; node_exporter.service [Unit] Description=Node Exporter [Service] User=node_exporter ExecStart=/usr/local/bin/node_exporter [Install] WantedBy=multi-user.target (https://github.com/prometheus/node_exporter/blob/master/examples/systemd/node_exporter.service を改変)\nこれでNode exporterが自動起動するようになった.\n動作確認時と同様にブラウザでアクセスしても同じメトリクスが表示される.\nPrometheusのインストール 次にPrometheusをインストールする.\nNode exporterと同様にブラウザでPrometheusのダウンロードページを開き, Architectureをarmv7にした状態でprometheus-2.15.2.linux-armv7.tar.gzをダウンロードするか, コマンドラインからwgetする.\n# 任意のディレクトリで実行 $ cd /path/to/workspace # prometheusをダウンロードして展開, 移動 $ wget https://github.com/prometheus/prometheus/releases/download/v2.15.2/prometheus-2.15.2.linux-armv7.tar.gz $ tar -xzf prometheus-2.15.2.linux-armv7.tar.gz $ sudo cp -a prometheus-2.15.2.linux-armv7 /usr/local/prometheus すでに起動中のNode exporterのメトリクスを取得するために設定ファイルを編集し, 動作確認する.\n# 設定ファイルを編集 $ sudo vim /usr/local/prometheus/prometheus.yml # Prometheusを起動 $ /usr/local/prometheus/prometheus --config.file=/usr/local/prometheus/prometheus.yml level=info ts=2020-01-15T14:14:00.683Z caller=main.go:294 msg=\u0026#34;no time or size retention was set so using the default time retention\u0026#34; duration=15d level=info ts=2020-01-15T14:14:00.684Z caller=main.go:330 msg=\u0026#34;Starting Prometheus\u0026#34; version=\u0026#34;(version=2.15.2, branch=HEAD, revision=d9613e5c466c6e9de548c4dae1b9aabf9aaf7c57)\u0026#34; ... level=info ts=2020-01-15T14:14:00.703Z caller=web.go:506 component=web msg=\u0026#34;Start listening for connections\u0026#34; address=0.0.0.0:9090 ... level=info ts=2020-01-15T14:14:00.748Z caller=main.go:617 msg=\u0026#34;Server is ready to receive web requests.\u0026#34; # 動作確認が終わり次第Ctrl+Cで終了する prometheus.yaml global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: \u0026#39;prometheus\u0026#39; static_configs: - targets: [\u0026#39;localhost:9090\u0026#39;] - job_name: \u0026#39;node\u0026#39; static_configs: - targets: [\u0026#39;localhost:9100\u0026#39;] 9090番ポートで起動しているので, Node exporterのときと同様に\u0026lt;ラズパイのIP\u0026gt;:9090をブラウザで開く.\nPrometheusのUI画面が表示される.\n試しにメニューバーからStatus-\u0026gt;Targetsと進むとNode exporterのメトリクスをモニタリングしていることがわかる.\nこちらもNode exporterと同じようにサービス化する.\n# service起動に必要なuser(prometheus)を追加する $ sudo useradd -U -s /sbin/nologin -M -d / prometheus # Prometheusが時系列データを保存するためのディレクトリを作成する $ sudo mkdir -p /var/lib/prometheus/data $ sudo chown -R prometheus:prometheus /var/lib/prometheus # serviceファイルを作成する $ sudo vim /etc/systemd/system/prometheus.service # serviceの起動 $ sudo systemctl daemon-reload $ sudo systemctl enable prometheus Created symlink /etc/systemd/system/multi-user.target.wants/prometheus.service → /etc/systemd/system/prometheus.service. $ sudo systemctl start prometheus $ systemctl status prometheus ● prometheus.service - Prometheus Server Loaded: loaded (/etc/systemd/system/prometheus.service; enabled; vendor preset: enabled) Active: active (running) since Sun 2020-01-19 12:14:50 JST; 14min ago Main PID: 20933 (prometheus) Tasks: 14 (limit: 2200) Memory: 27.8M CGroup: /system.slice/prometheus.service └─20933 /usr/local/prometheus/prometheus --config.file=/usr/local/prometheus/prometheus.yml --storage.tsdb.path=/var/lib/prometheus/data ... Jan 19 12:14:51 raspberrypi prometheus[20933]: level=info ts=2020-01-19T03:14:51.079Z caller=web.go:506 component=web msg=\u0026#34;Start listening for connections\u0026#34; address=0.0.0.0:9090 ... Jan 19 12:14:51 raspberrypi prometheus[20933]: level=info ts=2020-01-19T03:14:51.125Z caller=main.go:617 msg=\u0026#34;Server is ready to receive web requests.\u0026#34; prometheus.service [Unit] Description=Prometheus Server [Service] User=prometheus ExecStart=/usr/local/prometheus/prometheus \\ --config.file=/usr/local/prometheus/prometheus.yml \\ --storage.tsdb.path=/var/lib/prometheus/data [Install] WantedBy=multi-user.target これでPrometheusも自動起動するようになった.\nGrafanaのインストール こちらの手順を参考にGrafanaをインストールする.\n# devファイルからGrafanaをインストール $ sudo apt-get install -y adduser libfontconfig1 $ wget https://dl.grafana.com/oss/release/grafana_6.5.2_armhf.deb $ sudo dpkg -i grafana_6.5.2_armhf.deb # サービスを起動 $ sudo systemctl daemon-reload $ sudo systemctl enable grafana-server Synchronizing state of grafana-server.service with SysV service script with /lib/systemd/systemd-sysv-install. Executing: /lib/systemd/systemd-sysv-install enable grafana-server Created symlink /etc/systemd/system/multi-user.target.wants/grafana-server.service → /usr/lib/systemd/system/grafana-server.service. $ sudo systemctl start grafana-server $ systemctl status grafana-server ● grafana-server.service - Grafana instance Loaded: loaded (/usr/lib/systemd/system/grafana-server.service; enabled; vendor preset: enabled) Active: active (running) since Tue 2020-01-14 22:24:02 JST; 4 days ago Docs: http://docs.grafana.org Main PID: 7445 (grafana-server) Tasks: 16 (limit: 2200) Memory: 29.3M CGroup: /system.slice/grafana-server.service └─7445 /usr/sbin/grafana-server --config=/etc/grafana/grafana.ini --pidfile=/var/run/grafana/grafana-server.pid --packaging=deb cfg:default.paths.logs=/var/log/grafana cfg:default.paths.data=/v ... Jan 14 22:24:09 raspberrypi grafana-server[7445]: t=2020-01-14T22:24:09+0900 lvl=info msg=\u0026#34;HTTP Server Listen\u0026#34; logger=http.server address=[::]:3000 protocol=http subUrl= socket= ... 3000番ポートで起動しているので, ブラウザで\u0026lt;ラズパイのIP\u0026gt;:3000を開く.\nログイン画面が開かれるので, 初期ID(admin)とパスワード(admin)で入る.\nホームダッシュボードが開かれる.\nこれでGrafanaの立ち上げは完了.\nPrometheusのが取得したメトリクスを読めるようにdata sourceの設定を行う.\nホームダッシュボードのAdd data sourceを開き, Prometheusを選択する.\n設定画面でPrometheusのURL(http://localhost:9090)を入力し, Save \u0026amp; Testを押すとPrometheusが登録される.\n次はダッシュボードを作る.\nホームダッシュボードのNew dashboardを開き, Add Queryを選択する.\n編集画面で試しにメモリの空きを表すメトリクス名node_memory_MemAvailable_bytesを入力すると, その時系列データがグラフ化される.\nすごい.\n他にもPromQLとGrafanaの機能をいじり始めると楽しくて時間が飛ぶので割愛するが,\n同様にしてPrometheusが取得した各種メトリクスをグラフ化することができる.\n下の画像は今回作ったダッシュボードのスクリーンショットで,\nCPU使用率, メモリ使用率, ディスク使用率などが可視化されている.\nダッシュボードをJSON形式で出力したもの { \u0026#34;annotations\u0026#34;: { \u0026#34;list\u0026#34;: [ { \u0026#34;builtIn\u0026#34;: 1, \u0026#34;datasource\u0026#34;: \u0026#34;-- Grafana --\u0026#34;, \u0026#34;enable\u0026#34;: true, \u0026#34;hide\u0026#34;: true, \u0026#34;iconColor\u0026#34;: \u0026#34;rgba(0, 211, 255, 1)\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Annotations \u0026amp; Alerts\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;dashboard\u0026#34; } ] }, \u0026#34;editable\u0026#34;: true, \u0026#34;gnetId\u0026#34;: null, \u0026#34;graphTooltip\u0026#34;: 0, \u0026#34;id\u0026#34;: 4, \u0026#34;links\u0026#34;: [], \u0026#34;panels\u0026#34;: [ { \u0026#34;cacheTimeout\u0026#34;: null, \u0026#34;colorBackground\u0026#34;: true, \u0026#34;colorPrefix\u0026#34;: false, \u0026#34;colorValue\u0026#34;: false, \u0026#34;colors\u0026#34;: [ \u0026#34;#d44a3a\u0026#34;, \u0026#34;rgba(237, 129, 40, 0.89)\u0026#34;, \u0026#34;#299c46\u0026#34; ], \u0026#34;datasource\u0026#34;: null, \u0026#34;format\u0026#34;: \u0026#34;none\u0026#34;, \u0026#34;gauge\u0026#34;: { \u0026#34;maxValue\u0026#34;: 100, \u0026#34;minValue\u0026#34;: 0, \u0026#34;show\u0026#34;: false, \u0026#34;thresholdLabels\u0026#34;: false, \u0026#34;thresholdMarkers\u0026#34;: true }, \u0026#34;gridPos\u0026#34;: { \u0026#34;h\u0026#34;: 6, \u0026#34;w\u0026#34;: 6, \u0026#34;x\u0026#34;: 0, \u0026#34;y\u0026#34;: 0 }, \u0026#34;id\u0026#34;: 14, \u0026#34;interval\u0026#34;: null, \u0026#34;links\u0026#34;: [], \u0026#34;mappingType\u0026#34;: 1, \u0026#34;mappingTypes\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;value to text\u0026#34;, \u0026#34;value\u0026#34;: 1 }, { \u0026#34;name\u0026#34;: \u0026#34;range to text\u0026#34;, \u0026#34;value\u0026#34;: 2 } ], \u0026#34;maxDataPoints\u0026#34;: 100, \u0026#34;nullPointMode\u0026#34;: \u0026#34;connected\u0026#34;, \u0026#34;nullText\u0026#34;: null, \u0026#34;options\u0026#34;: {}, \u0026#34;postfix\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;postfixFontSize\u0026#34;: \u0026#34;50%\u0026#34;, \u0026#34;prefix\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;prefixFontSize\u0026#34;: \u0026#34;50%\u0026#34;, \u0026#34;rangeMaps\u0026#34;: [ { \u0026#34;from\u0026#34;: \u0026#34;null\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;N/A\u0026#34;, \u0026#34;to\u0026#34;: \u0026#34;null\u0026#34; } ], \u0026#34;sparkline\u0026#34;: { \u0026#34;fillColor\u0026#34;: \u0026#34;rgba(31, 118, 189, 0.18)\u0026#34;, \u0026#34;full\u0026#34;: false, \u0026#34;lineColor\u0026#34;: \u0026#34;rgb(31, 120, 193)\u0026#34;, \u0026#34;show\u0026#34;: false, \u0026#34;ymax\u0026#34;: null, \u0026#34;ymin\u0026#34;: null }, \u0026#34;tableColumn\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;targets\u0026#34;: [ { \u0026#34;expr\u0026#34;: \u0026#34;up{job=\\\u0026#34;node\\\u0026#34;}\u0026#34;, \u0026#34;refId\u0026#34;: \u0026#34;A\u0026#34; } ], \u0026#34;thresholds\u0026#34;: \u0026#34;0.1, 0.9\u0026#34;, \u0026#34;timeFrom\u0026#34;: null, \u0026#34;timeShift\u0026#34;: null, \u0026#34;title\u0026#34;: \u0026#34;Node exporter\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;singlestat\u0026#34;, \u0026#34;valueFontSize\u0026#34;: \u0026#34;200%\u0026#34;, \u0026#34;valueMaps\u0026#34;: [ { \u0026#34;op\u0026#34;: \u0026#34;=\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;N/A\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;null\u0026#34; }, { \u0026#34;op\u0026#34;: \u0026#34;=\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;down\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;0\u0026#34; }, { \u0026#34;op\u0026#34;: \u0026#34;=\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;up\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;1\u0026#34; } ], \u0026#34;valueName\u0026#34;: \u0026#34;current\u0026#34; }, { \u0026#34;datasource\u0026#34;: null, \u0026#34;gridPos\u0026#34;: { \u0026#34;h\u0026#34;: 6, \u0026#34;w\u0026#34;: 6, \u0026#34;x\u0026#34;: 6, \u0026#34;y\u0026#34;: 0 }, \u0026#34;id\u0026#34;: 8, \u0026#34;options\u0026#34;: { \u0026#34;fieldOptions\u0026#34;: { \u0026#34;calcs\u0026#34;: [ \u0026#34;last\u0026#34; ], \u0026#34;defaults\u0026#34;: { \u0026#34;mappings\u0026#34;: [], \u0026#34;max\u0026#34;: 1, \u0026#34;min\u0026#34;: 0, \u0026#34;thresholds\u0026#34;: [ { \u0026#34;color\u0026#34;: \u0026#34;green\u0026#34;, \u0026#34;value\u0026#34;: null }, { \u0026#34;color\u0026#34;: \u0026#34;#E24D42\u0026#34;, \u0026#34;value\u0026#34;: 0.5 }, { \u0026#34;color\u0026#34;: \u0026#34;#EAB839\u0026#34;, \u0026#34;value\u0026#34;: 0.8 } ], \u0026#34;unit\u0026#34;: \u0026#34;percentunit\u0026#34; }, \u0026#34;override\u0026#34;: {}, \u0026#34;values\u0026#34;: false }, \u0026#34;orientation\u0026#34;: \u0026#34;auto\u0026#34;, \u0026#34;showThresholdLabels\u0026#34;: true, \u0026#34;showThresholdMarkers\u0026#34;: true }, \u0026#34;pluginVersion\u0026#34;: \u0026#34;6.5.2\u0026#34;, \u0026#34;targets\u0026#34;: [ { \u0026#34;expr\u0026#34;: \u0026#34;1.0 - (avg without(cpu) (rate(node_cpu_seconds_total{mode=\\\u0026#34;idle\\\u0026#34;}[1m])))\u0026#34;, \u0026#34;refId\u0026#34;: \u0026#34;A\u0026#34; } ], \u0026#34;timeFrom\u0026#34;: null, \u0026#34;timeShift\u0026#34;: null, \u0026#34;title\u0026#34;: \u0026#34;CPU Usage\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;gauge\u0026#34; }, { \u0026#34;cacheTimeout\u0026#34;: null, \u0026#34;datasource\u0026#34;: null, \u0026#34;gridPos\u0026#34;: { \u0026#34;h\u0026#34;: 6, \u0026#34;w\u0026#34;: 6, \u0026#34;x\u0026#34;: 12, \u0026#34;y\u0026#34;: 0 }, \u0026#34;id\u0026#34;: 6, \u0026#34;links\u0026#34;: [], \u0026#34;options\u0026#34;: { \u0026#34;fieldOptions\u0026#34;: { \u0026#34;calcs\u0026#34;: [ \u0026#34;last\u0026#34; ], \u0026#34;defaults\u0026#34;: { \u0026#34;links\u0026#34;: [], \u0026#34;mappings\u0026#34;: [], \u0026#34;max\u0026#34;: 1, \u0026#34;min\u0026#34;: 0, \u0026#34;nullValueMode\u0026#34;: \u0026#34;connected\u0026#34;, \u0026#34;thresholds\u0026#34;: [ { \u0026#34;color\u0026#34;: \u0026#34;green\u0026#34;, \u0026#34;value\u0026#34;: null }, { \u0026#34;color\u0026#34;: \u0026#34;#EAB839\u0026#34;, \u0026#34;value\u0026#34;: 0.5 }, { \u0026#34;color\u0026#34;: \u0026#34;#E24D42\u0026#34;, \u0026#34;value\u0026#34;: 0.8 } ], \u0026#34;title\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;unit\u0026#34;: \u0026#34;percentunit\u0026#34; }, \u0026#34;override\u0026#34;: {}, \u0026#34;values\u0026#34;: false }, \u0026#34;orientation\u0026#34;: \u0026#34;horizontal\u0026#34;, \u0026#34;showThresholdLabels\u0026#34;: true, \u0026#34;showThresholdMarkers\u0026#34;: true }, \u0026#34;pluginVersion\u0026#34;: \u0026#34;6.5.2\u0026#34;, \u0026#34;targets\u0026#34;: [ { \u0026#34;expr\u0026#34;: \u0026#34;(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes\u0026#34;, \u0026#34;instant\u0026#34;: false, \u0026#34;refId\u0026#34;: \u0026#34;A\u0026#34; } ], \u0026#34;timeFrom\u0026#34;: null, \u0026#34;timeShift\u0026#34;: null, \u0026#34;title\u0026#34;: \u0026#34;Memory Usage\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;gauge\u0026#34; }, { \u0026#34;datasource\u0026#34;: null, \u0026#34;gridPos\u0026#34;: { \u0026#34;h\u0026#34;: 6, \u0026#34;w\u0026#34;: 6, \u0026#34;x\u0026#34;: 18, \u0026#34;y\u0026#34;: 0 }, \u0026#34;id\u0026#34;: 12, \u0026#34;options\u0026#34;: { \u0026#34;fieldOptions\u0026#34;: { \u0026#34;calcs\u0026#34;: [ \u0026#34;last\u0026#34; ], \u0026#34;defaults\u0026#34;: { \u0026#34;mappings\u0026#34;: [], \u0026#34;max\u0026#34;: 1, \u0026#34;min\u0026#34;: 0, \u0026#34;thresholds\u0026#34;: [ { \u0026#34;color\u0026#34;: \u0026#34;green\u0026#34;, \u0026#34;value\u0026#34;: null }, { \u0026#34;color\u0026#34;: \u0026#34;red\u0026#34;, \u0026#34;value\u0026#34;: 0.5 }, { \u0026#34;color\u0026#34;: \u0026#34;#EAB839\u0026#34;, \u0026#34;value\u0026#34;: 0.8 } ], \u0026#34;unit\u0026#34;: \u0026#34;percentunit\u0026#34; }, \u0026#34;override\u0026#34;: {}, \u0026#34;values\u0026#34;: false }, \u0026#34;orientation\u0026#34;: \u0026#34;auto\u0026#34;, \u0026#34;showThresholdLabels\u0026#34;: true, \u0026#34;showThresholdMarkers\u0026#34;: true }, \u0026#34;pluginVersion\u0026#34;: \u0026#34;6.5.2\u0026#34;, \u0026#34;targets\u0026#34;: [ { \u0026#34;expr\u0026#34;: \u0026#34;1 - (node_filesystem_avail_bytes{mountpoint=\\\u0026#34;/\\\u0026#34;} / node_filesystem_size_bytes{mountpoint=\\\u0026#34;/\\\u0026#34;})\u0026#34;, \u0026#34;refId\u0026#34;: \u0026#34;A\u0026#34; } ], \u0026#34;timeFrom\u0026#34;: null, \u0026#34;timeShift\u0026#34;: null, \u0026#34;title\u0026#34;: \u0026#34;Disk Usage\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;gauge\u0026#34; }, { \u0026#34;aliasColors\u0026#34;: {}, \u0026#34;bars\u0026#34;: false, \u0026#34;dashLength\u0026#34;: 10, \u0026#34;dashes\u0026#34;: false, \u0026#34;datasource\u0026#34;: null, \u0026#34;fill\u0026#34;: 1, \u0026#34;fillGradient\u0026#34;: 0, \u0026#34;gridPos\u0026#34;: { \u0026#34;h\u0026#34;: 8, \u0026#34;w\u0026#34;: 12, \u0026#34;x\u0026#34;: 0, \u0026#34;y\u0026#34;: 6 }, \u0026#34;hiddenSeries\u0026#34;: false, \u0026#34;id\u0026#34;: 2, \u0026#34;legend\u0026#34;: { \u0026#34;avg\u0026#34;: false, \u0026#34;current\u0026#34;: true, \u0026#34;max\u0026#34;: false, \u0026#34;min\u0026#34;: false, \u0026#34;show\u0026#34;: true, \u0026#34;total\u0026#34;: false, \u0026#34;values\u0026#34;: true }, \u0026#34;lines\u0026#34;: true, \u0026#34;linewidth\u0026#34;: 1, \u0026#34;nullPointMode\u0026#34;: \u0026#34;null\u0026#34;, \u0026#34;options\u0026#34;: { \u0026#34;dataLinks\u0026#34;: [] }, \u0026#34;percentage\u0026#34;: false, \u0026#34;pointradius\u0026#34;: 2, \u0026#34;points\u0026#34;: false, \u0026#34;renderer\u0026#34;: \u0026#34;flot\u0026#34;, \u0026#34;seriesOverrides\u0026#34;: [], \u0026#34;spaceLength\u0026#34;: 10, \u0026#34;stack\u0026#34;: false, \u0026#34;steppedLine\u0026#34;: false, \u0026#34;targets\u0026#34;: [ { \u0026#34;expr\u0026#34;: \u0026#34;avg without(cpu) (rate(node_cpu_seconds_total{mode!=\\\u0026#34;idle\\\u0026#34;}[1m]))\u0026#34;, \u0026#34;legendFormat\u0026#34;: \u0026#34;{{mode}}\u0026#34;, \u0026#34;refId\u0026#34;: \u0026#34;A\u0026#34; } ], \u0026#34;thresholds\u0026#34;: [], \u0026#34;timeFrom\u0026#34;: null, \u0026#34;timeRegions\u0026#34;: [], \u0026#34;timeShift\u0026#34;: null, \u0026#34;title\u0026#34;: \u0026#34;CPU Usage\u0026#34;, \u0026#34;tooltip\u0026#34;: { \u0026#34;shared\u0026#34;: true, \u0026#34;sort\u0026#34;: 0, \u0026#34;value_type\u0026#34;: \u0026#34;individual\u0026#34; }, \u0026#34;type\u0026#34;: \u0026#34;graph\u0026#34;, \u0026#34;xaxis\u0026#34;: { \u0026#34;buckets\u0026#34;: null, \u0026#34;mode\u0026#34;: \u0026#34;time\u0026#34;, \u0026#34;name\u0026#34;: null, \u0026#34;show\u0026#34;: true, \u0026#34;values\u0026#34;: [] }, \u0026#34;yaxes\u0026#34;: [ { \u0026#34;format\u0026#34;: \u0026#34;percentunit\u0026#34;, \u0026#34;label\u0026#34;: null, \u0026#34;logBase\u0026#34;: 1, \u0026#34;max\u0026#34;: null, \u0026#34;min\u0026#34;: null, \u0026#34;show\u0026#34;: true }, { \u0026#34;format\u0026#34;: \u0026#34;short\u0026#34;, \u0026#34;label\u0026#34;: null, \u0026#34;logBase\u0026#34;: 1, \u0026#34;max\u0026#34;: null, \u0026#34;min\u0026#34;: null, \u0026#34;show\u0026#34;: true } ], \u0026#34;yaxis\u0026#34;: { \u0026#34;align\u0026#34;: false, \u0026#34;alignLevel\u0026#34;: null } }, { \u0026#34;aliasColors\u0026#34;: {}, \u0026#34;bars\u0026#34;: false, \u0026#34;dashLength\u0026#34;: 10, \u0026#34;dashes\u0026#34;: false, \u0026#34;datasource\u0026#34;: null, \u0026#34;fill\u0026#34;: 1, \u0026#34;fillGradient\u0026#34;: 0, \u0026#34;gridPos\u0026#34;: { \u0026#34;h\u0026#34;: 8, \u0026#34;w\u0026#34;: 12, \u0026#34;x\u0026#34;: 12, \u0026#34;y\u0026#34;: 6 }, \u0026#34;hiddenSeries\u0026#34;: false, \u0026#34;id\u0026#34;: 4, \u0026#34;legend\u0026#34;: { \u0026#34;avg\u0026#34;: false, \u0026#34;current\u0026#34;: true, \u0026#34;max\u0026#34;: false, \u0026#34;min\u0026#34;: false, \u0026#34;show\u0026#34;: true, \u0026#34;total\u0026#34;: false, \u0026#34;values\u0026#34;: true }, \u0026#34;lines\u0026#34;: true, \u0026#34;linewidth\u0026#34;: 1, \u0026#34;nullPointMode\u0026#34;: \u0026#34;null\u0026#34;, \u0026#34;options\u0026#34;: { \u0026#34;dataLinks\u0026#34;: [] }, \u0026#34;percentage\u0026#34;: false, \u0026#34;pointradius\u0026#34;: 2, \u0026#34;points\u0026#34;: false, \u0026#34;renderer\u0026#34;: \u0026#34;flot\u0026#34;, \u0026#34;seriesOverrides\u0026#34;: [], \u0026#34;spaceLength\u0026#34;: 10, \u0026#34;stack\u0026#34;: false, \u0026#34;steppedLine\u0026#34;: false, \u0026#34;targets\u0026#34;: [ { \u0026#34;expr\u0026#34;: \u0026#34;node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes\u0026#34;, \u0026#34;instant\u0026#34;: false, \u0026#34;intervalFactor\u0026#34;: 1, \u0026#34;legendFormat\u0026#34;: \u0026#34;Memory in Use\u0026#34;, \u0026#34;refId\u0026#34;: \u0026#34;A\u0026#34; }, { \u0026#34;expr\u0026#34;: \u0026#34;node_memory_MemTotal_bytes\u0026#34;, \u0026#34;legendFormat\u0026#34;: \u0026#34;Memory Total\u0026#34;, \u0026#34;refId\u0026#34;: \u0026#34;B\u0026#34; } ], \u0026#34;thresholds\u0026#34;: [], \u0026#34;timeFrom\u0026#34;: null, \u0026#34;timeRegions\u0026#34;: [], \u0026#34;timeShift\u0026#34;: null, \u0026#34;title\u0026#34;: \u0026#34;Memory Usage\u0026#34;, \u0026#34;tooltip\u0026#34;: { \u0026#34;shared\u0026#34;: true, \u0026#34;sort\u0026#34;: 0, \u0026#34;value_type\u0026#34;: \u0026#34;individual\u0026#34; }, \u0026#34;type\u0026#34;: \u0026#34;graph\u0026#34;, \u0026#34;xaxis\u0026#34;: { \u0026#34;buckets\u0026#34;: null, \u0026#34;mode\u0026#34;: \u0026#34;time\u0026#34;, \u0026#34;name\u0026#34;: null, \u0026#34;show\u0026#34;: true, \u0026#34;values\u0026#34;: [] }, \u0026#34;yaxes\u0026#34;: [ { \u0026#34;format\u0026#34;: \u0026#34;decbytes\u0026#34;, \u0026#34;label\u0026#34;: null, \u0026#34;logBase\u0026#34;: 1, \u0026#34;max\u0026#34;: null, \u0026#34;min\u0026#34;: \u0026#34;0\u0026#34;, \u0026#34;show\u0026#34;: true }, { \u0026#34;format\u0026#34;: \u0026#34;short\u0026#34;, \u0026#34;label\u0026#34;: null, \u0026#34;logBase\u0026#34;: 1, \u0026#34;max\u0026#34;: null, \u0026#34;min\u0026#34;: null, \u0026#34;show\u0026#34;: true } ], \u0026#34;yaxis\u0026#34;: { \u0026#34;align\u0026#34;: false, \u0026#34;alignLevel\u0026#34;: null } }, { \u0026#34;aliasColors\u0026#34;: {}, \u0026#34;bars\u0026#34;: false, \u0026#34;dashLength\u0026#34;: 10, \u0026#34;dashes\u0026#34;: false, \u0026#34;datasource\u0026#34;: null, \u0026#34;fill\u0026#34;: 1, \u0026#34;fillGradient\u0026#34;: 0, \u0026#34;gridPos\u0026#34;: { \u0026#34;h\u0026#34;: 8, \u0026#34;w\u0026#34;: 12, \u0026#34;x\u0026#34;: 0, \u0026#34;y\u0026#34;: 14 }, \u0026#34;hiddenSeries\u0026#34;: false, \u0026#34;id\u0026#34;: 10, \u0026#34;legend\u0026#34;: { \u0026#34;avg\u0026#34;: false, \u0026#34;current\u0026#34;: false, \u0026#34;max\u0026#34;: false, \u0026#34;min\u0026#34;: false, \u0026#34;show\u0026#34;: true, \u0026#34;total\u0026#34;: false, \u0026#34;values\u0026#34;: false }, \u0026#34;lines\u0026#34;: true, \u0026#34;linewidth\u0026#34;: 1, \u0026#34;nullPointMode\u0026#34;: \u0026#34;null\u0026#34;, \u0026#34;options\u0026#34;: { \u0026#34;dataLinks\u0026#34;: [] }, \u0026#34;percentage\u0026#34;: false, \u0026#34;pointradius\u0026#34;: 2, \u0026#34;points\u0026#34;: false, \u0026#34;renderer\u0026#34;: \u0026#34;flot\u0026#34;, \u0026#34;seriesOverrides\u0026#34;: [], \u0026#34;spaceLength\u0026#34;: 10, \u0026#34;stack\u0026#34;: false, \u0026#34;steppedLine\u0026#34;: false, \u0026#34;targets\u0026#34;: [ { \u0026#34;expr\u0026#34;: \u0026#34;node_filesystem_avail_bytes{mountpoint=\\\u0026#34;/\\\u0026#34;}\u0026#34;, \u0026#34;refId\u0026#34;: \u0026#34;A\u0026#34; }, { \u0026#34;expr\u0026#34;: \u0026#34;node_filesystem_size_bytes{mountpoint=\\\u0026#34;/\\\u0026#34;}\u0026#34;, \u0026#34;refId\u0026#34;: \u0026#34;B\u0026#34; } ], \u0026#34;thresholds\u0026#34;: [], \u0026#34;timeFrom\u0026#34;: null, \u0026#34;timeRegions\u0026#34;: [], \u0026#34;timeShift\u0026#34;: null, \u0026#34;title\u0026#34;: \u0026#34;Disk Usage\u0026#34;, \u0026#34;tooltip\u0026#34;: { \u0026#34;shared\u0026#34;: true, \u0026#34;sort\u0026#34;: 0, \u0026#34;value_type\u0026#34;: \u0026#34;individual\u0026#34; }, \u0026#34;type\u0026#34;: \u0026#34;graph\u0026#34;, \u0026#34;xaxis\u0026#34;: { \u0026#34;buckets\u0026#34;: null, \u0026#34;mode\u0026#34;: \u0026#34;time\u0026#34;, \u0026#34;name\u0026#34;: null, \u0026#34;show\u0026#34;: true, \u0026#34;values\u0026#34;: [] }, \u0026#34;yaxes\u0026#34;: [ { \u0026#34;format\u0026#34;: \u0026#34;decbytes\u0026#34;, \u0026#34;label\u0026#34;: null, \u0026#34;logBase\u0026#34;: 1, \u0026#34;max\u0026#34;: null, \u0026#34;min\u0026#34;: \u0026#34;0\u0026#34;, \u0026#34;show\u0026#34;: true }, { \u0026#34;format\u0026#34;: \u0026#34;short\u0026#34;, \u0026#34;label\u0026#34;: null, \u0026#34;logBase\u0026#34;: 1, \u0026#34;max\u0026#34;: null, \u0026#34;min\u0026#34;: null, \u0026#34;show\u0026#34;: true } ], \u0026#34;yaxis\u0026#34;: { \u0026#34;align\u0026#34;: false, \u0026#34;alignLevel\u0026#34;: null } } ], \u0026#34;refresh\u0026#34;: \u0026#34;30s\u0026#34;, \u0026#34;schemaVersion\u0026#34;: 21, \u0026#34;style\u0026#34;: \u0026#34;dark\u0026#34;, \u0026#34;tags\u0026#34;: [], \u0026#34;templating\u0026#34;: { \u0026#34;list\u0026#34;: [] }, \u0026#34;time\u0026#34;: { \u0026#34;from\u0026#34;: \u0026#34;now-6h\u0026#34;, \u0026#34;to\u0026#34;: \u0026#34;now\u0026#34; }, \u0026#34;timepicker\u0026#34;: { \u0026#34;refresh_intervals\u0026#34;: [ \u0026#34;5s\u0026#34;, \u0026#34;10s\u0026#34;, \u0026#34;30s\u0026#34;, \u0026#34;1m\u0026#34;, \u0026#34;5m\u0026#34;, \u0026#34;15m\u0026#34;, \u0026#34;30m\u0026#34;, \u0026#34;1h\u0026#34;, \u0026#34;2h\u0026#34;, \u0026#34;1d\u0026#34; ] }, \u0026#34;timezone\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;title\u0026#34;: \u0026#34;Node exporter\u0026#34;, \u0026#34;uid\u0026#34;: \u0026#34;JuC2wgWgk\u0026#34;, \u0026#34;version\u0026#34;: 22 } やったぜ.\nおわり Node exporterが吐き出したメトリクスをPrometheusで収集し,\nそれをGrafanaでグラフ化するまでの流れをラズパイで試すことができた.\nオタクなのでいろんな情報が1つのダッシュボードに表示されていると気持ちよくなってしまう\u0026hellip;\nGrafanaは色んな意味で時間泥棒.\n後で気が向いたらAlertmanagerを入れてみる.\nおまけ ","date":"2020-01-15T21:31:01+09:00","image":"/post/2020-01-15-prometheus-grafana-raspberry-pi/2020-01-15-sotochan.jpg","permalink":"/post/2020-01-15-prometheus-grafana-raspberry-pi/","title":"Raspberry PiにPrometheusとGrafanaを突っ込んだ"},{"content":"部屋のラズパイにSSHとかしたい 適当に作ったアプリの動作確認とかでたまに使ってるラズパイが部屋に転がってるんだけど,\n使うたびに画面につないだりするのが面倒なので部屋のMacBookからサクッとSSHとかVNCで作業できるようにした.\nやったことのまとめ ラズパイにSSHする設定を行った ラズパイのIPを固定した ついでにVNCでGUIを触れるようにした つかうもの Raspberry Pi 3 Model B+ IP固定したいラズパイ ルーターに無線接続している状態 OSはRaspbian(10.0) E-WMTA2.3 うちにあるルーター SoftBank光にすると使わざるを得ないやつ スペック MacBook ラズパイと同じルーターに無線接続してるコンピュータ 動作確認くらいにしか使わないのでSSHが使えればなんでも良い やったこと ラズパイのSSH設定 ラズパイのIPアドレス固定 VNCの設定 ラズパイのSSH設定 まずはラズパイにSSHできるように設定する.\nラズパイのデスクトップ左上のメニューから,\nPreferences-\u0026gt;Raspberry Pi Configuration-\u0026gt;Interfacesと進み, SSHのEnableを選択してOKを押す.\nこれでSSHの準備は完了.\n試しにラズパイのIPが固定されていない状態でMacBookからSSHしてみる.\nまずはラズパイ側でIPアドレスを確認.\n# ラズパイで実行 # 無線接続しているのでwlan0のinetを見る $ ifconfig eth0: ... lo: ... wlan0: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 1500 inet 192.168.3.11 netmask 255.255.255.0 broadcast 192.168.3.255 ... 以上よりラズパイのIP(DHCPで割り振られたもの)は 192.168.3.11 であることがわかったので, MacBookからSSHしてみる.\n# MacBookで実行 $ hostname macbook.local # ラズパイのデフォルトユーザー(pi)で入る # 先程確認したラズパイのIPを指定 $ ssh pi@192.168.3.11 pi@192.168.3.11\u0026#39;s password: # piのパスワードを入力 Linux raspberrypi 4.19.50-v7+ #896 SMP Thu Jun 20 16:11:44 BST 2019 armv7l The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. Last login: Tue Jan 7 00:21:34 2020 from 192.168.3.2 [pi@192.168.3.11]$ hostname raspberrypi やったぜ.\nラズパイにSSHできた.\nラズパイのIPアドレス固定 上記の手順だとラズパイの電源を入れ直したときなどDHCPで新しいIPが振られるたびに確認が必要になって手間になるので,\n次はラズパイのIPをDHCPではなく固定するようにする.\nまずはこちらの手順を参考にブラウザで以下のアドレスからルーター(E-WMTA2.3)の設定画面を開く.\nラズパイのブラウザは見づらいので自分はMacBookのChromeを使った.\nhttp://172.16.255.254/\nルーター機能の設定-\u0026gt;IPアドレス／DHCPサーバの設定と進んで割当IPアドレスの範囲を確認する.\nこの場合は 192.168.3.2 ~ 192.168.3.254 までがDHCPで使われるIPになっている.\n(他のルーターでも同じようにDHCPで使うIPの範囲を確認できるはず)\nおそらくこの手順を使えばラズパイ側で設定しなくてもラズパイのMACアドレスに対してIPを固定できるんだけど(正確にはDHCPで振られるIPが固定される?),\n今回はラズパイの設定をいじる形でやってみる.\nこのままだと他のデバイスが増えた場合にDHCPで使われる範囲が固定したいIPとかぶるかもしれない(ほんまか?)ので, 念の為範囲を 192.168.3.2 ~ 192.168.3.128 に変更しておく.\nルーターを再起動する.\n次にラズパイの設定ファイル/etc/dhcpcd.confを編集し, 任意のIPで固定する.\n今回は 192.168.3.200 で固定する.\n# 以下は全てラズパイで実行 # デフォルトゲートウェイの確認 $ route Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface default 192.168.3.1 0.0.0.0 UG 303 0 0 wlan0 # この行の情報を使う 192.168.3.0 0.0.0.0 255.255.255.0 U 303 0 0 wlan0 # 設定ファイルを編集 $ vim /etc/dhcpcd.conf ... # 以下の記述を末尾に追加 interface wlan0 # routeで確認したIface static ip_address=192.168.3.200/24 # 任意の固定IP static routers=192.168.3.1 # Gateway static domain_name_servers=192.168.3.1 # 特にDNSの設定をしていなければroutersと同じ値で問題ないはず 編集後, ラズパイを再起動する\u0026hellip;\n# 再度ラズパイでIPを確認する $ ifconfig eth0: ... lo: ... wlan0: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 1500 inet 192.168.3.200 netmask 255.255.255.0 broadcast 192.168.3.255 ... 電源何回入れ直しても同じIPになる.\nどうやら指定したIPで固定されたっぽい.\n今度はMacBookから固定IPを指定してSSHする.\n# MacBookで実行 # 固定IPを指定 $ ssh pi@192.168.3.200 The authenticity of host \u0026#39;192.168.3.200 (192.168.3.200)\u0026#39; can\u0026#39;t be established. ECDSA key fingerprint is SHA256:... Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added \u0026#39;192.168.3.200\u0026#39; (ECDSA) to the list of known hosts. pi@192.168.3.200\u0026#39;s password: Linux raspberrypi 4.19.50-v7+ #896 SMP Thu Jun 20 16:11:44 BST 2019 armv7l The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. Last login: Tue Jan 7 01:45:23 2020 from 192.168.3.5 [pi@192.168.3.200]$ hostname raspberrypi できた.\nやったぜ.\nVNCの設定 SSHできるようになったので, ついでにVNCでGUIをいじれるようにする.\nラズパイのGUIはあんまり触らなさそうだけど, 念の為.\nといってもRaspbianにはRealVNCが最初から入っているので, 必要な設定はほとんどない.\nSSHを有効にしたときと同様にやればいい.\nラズパイのデスクトップ左上のメニューから,\nPreferences-\u0026gt;Raspberry Pi Configuration-\u0026gt;Interfacesと進み, VNCのEnableを選択してOKを押す.\nラズパイ側の準備は完了.\nMacBookに最初から入ってる画面共有.appで固定IPを指定して接続を試みると失敗する.\n対処法がよくわかんないので普通にVNC ViewerをMacBookにインストールして起動.\n検索窓みたいなやつにラズパイのIPを打ち込んでEnterを押すとなんか色々聞かれるけど問題なし.\nラズパイのユーザー名とパスワードを聞かれるので入力.\nラズパイの画面が開く.\nやったぜ.\nこのままだと解像度が低くてストレスがマッハなので,\nラズパイ側で再度設定を行う.\nラズパイのデスクトップ左上のメニューから,\nPreferences-\u0026gt;Raspberry Pi Configuration-\u0026gt;Systemと進み, ResolutionをDefaultから好みの解像度(自分の場合はDMT Mode 82)に変更する.\nラズパイの再起動を求められるのでYesを押す.\n少し時間がかかるが, ラズパイが再起動すると自動的にVNC Viewerの画面も再接続され,\n解像度が上がった画面が表示される.\nやったぜ.\nこれでGUIも使えるようになった(たぶんあんまり使わない).\nおわり 以上の手順で家の中のパソコンからいつでもラズパイに接続できるようになった.\nラズパイに毎回ディスプレイとキーボード繋ぐのめんどくさかったし, もっと早くやればよかった\u0026hellip;\nルーターでポート周りの設定をすれば家の外からも接続できるっぽいんだけど,\n自宅サーバーの運用とかよくわかってないしちょっとセキュリティ的にも怖いので当分の間はこれで遊ぶつもり.\nおまけ ","date":"2020-01-07T11:35:15+09:00","image":"/post/2020-01-07-raspberry-pi-static-id/2020-01-07-sotochan.jpg","permalink":"/post/2020-01-07-raspberry-pi-static-id/","title":"Raspberry PiのIPを固定していろいろできるようにした"},{"content":"完全に理解したい Kubernetes完全ガイドをまじめに読み始めたのでちょっとずつまとめていきたい.\n1~3章はDockerのおさらいだったりKubernetesとは?みたいな話だったので割愛.\n読んだもの Kubernetes完全ガイド 4章(APIリソースとkubectl) 重要そうなところとかよく使いそうなところだけまとめる.\n読んだことのまとめ Kubernetesには役割の異なるたくさんのリソースがある. Namespaceによりクラスタを仮想的に分離できる. リソースに関する操作はKubernetes APIを通じて行う. CLIならkubectlでいろいろ操作できる. Kubernetesリソースの種類 Kubernetesのリソースはだいたい5種類に分けられるらしい.\nWorkloads コンテナを実行する部分に関わるリソース. 基本的にPodと各種Controllerで構成される.\n重要そうなものだけ以下にまとめる.\nリソース 概要 Pod Kubernetesの最小単位 ReplicaSet Podのレプリカを複数維持する Deployment ReplicaSetを複数管理する DaemonSet 各NodeにPodを1台ずつ維持する StatefulSet ステートフルなDeploymentとPodを管理する Job 回数制限付きのPodの処理を行う CronJob Jobのスケジューリングを行う Discovery \u0026amp; LB コンテナの通信に関わるリソース.\nリソース 概要 Service 複数のPodで構成されるアプリケーションを外部に公開するL4レベルのLB Ingress 外部からServiceへのアクセスを可能にするL7レベルのLB Config \u0026amp; Storage コンテナの設定ファイルや機密情報, 永続化ボリュームに関するリソース.\nリソース 概要 Secret Podで扱うパスワードや鍵などの機密情報を管理する ConfigMap Podで扱う設定情報を管理する PersistentVolumeClaim PersistentVolume(後述)を管理する Cluster クラスタ自体の挙動に関するリソース.\nリソース 概要 Node Masterによって管理されるワーカーマシン Namespace クラスタを仮想的に分離させる PersistentVolume Podから利用できる永続化ボリューム ResourceQuota Namespaceごとに使用できるリソースの数に制限をかける ServiceAccount Namespaceに紐付いたユーザ情報 Role Namespaceレベルで許可される操作の情報 ClusterRole クラスタレベルで許可される操作の情報 RoleBinding Namespaceレベルでユーザ情報とRoleを紐付けて権限管理を行う ClusterRoleBinding クラスタレベルでユーザ情報とClusterRoleを紐付けて権限管理を行う NetworkPolicy クラスタ内のPod同士の通信を制限する Metadata クラスタ内の他のリソースを操作するためのリソース.\nリソース 概要 LimitRange NamespaceレベルでPodが使用できるCPUやメモリの制限を行う HorizontalPodAutoscaler DeploymentまたはReplicaSetのオートスケーリングを行う PodDisruptionBudget Node更新処理の際に維持するPodの数を設定する CustomRessourceDefinition 独自のリソースを作ってKubernetesを拡張する Namespaceによる仮想クラスタ分離 クラスタ作成時にデフォルトで作成されるNamespaceは3つ.\nkube-system クラスタの各種コンポーネントやアドオンといったシステムがデプロイされる kube-public 全ユーザが使用可能なConfigMapなどの情報が配置される default ユーザーがデフォルトで使用し, 任意のリソースが作成される # Namespaceの一覧を表示 $ kubectl get namespaces NAME STATUS AGE default Active 90m kube-public Active 90m kube-system Active 90m kubectl kubectlはMasterのkube-apiserverと通信を行うCLIツール.\nKubernetes API自体はRESTfulであるから代わりに各言語のクライアントライブラリを使うこともできる.\n# ローカルからAPIにアクセスするためにproxyを別ターミナルで起動 $ kubectl proxy Starting to serve on 127.0.0.1:8001 # APIを直接叩いてNamespaceの一覧を表示 # $(kubectl get namespaces)と同じ情報を取得できる $ curl 127.0.0.1:8001/api/v1/namespaces { \u0026#34;kind\u0026#34;: \u0026#34;NamespaceList\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;metadata\u0026#34;: { \u0026#34;selfLink\u0026#34;: \u0026#34;/api/v1/namespaces\u0026#34;, \u0026#34;resourceVersion\u0026#34;: \u0026#34;23058\u0026#34; }, \u0026#34;items\u0026#34;: [ { \u0026#34;metadata\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;selfLink\u0026#34;: \u0026#34;/api/v1/namespaces/default\u0026#34;, \u0026#34;uid\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;resourceVersion\u0026#34;: \u0026#34;25\u0026#34;, \u0026#34;creationTimestamp\u0026#34;: \u0026#34;2019-12-16T12:29:50Z\u0026#34; }, \u0026#34;spec\u0026#34;: { \u0026#34;finalizers\u0026#34;: [ \u0026#34;kubernetes\u0026#34; ] }, \u0026#34;status\u0026#34;: { \u0026#34;phase\u0026#34;: \u0026#34;Active\u0026#34; } }, { \u0026#34;metadata\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;kube-public\u0026#34;, \u0026#34;selfLink\u0026#34;: \u0026#34;/api/v1/namespaces/kube-public\u0026#34;, \u0026#34;uid\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;resourceVersion\u0026#34;: \u0026#34;35\u0026#34;, \u0026#34;creationTimestamp\u0026#34;: \u0026#34;2019-12-16T12:29:50Z\u0026#34; }, \u0026#34;spec\u0026#34;: { \u0026#34;finalizers\u0026#34;: [ \u0026#34;kubernetes\u0026#34; ] }, \u0026#34;status\u0026#34;: { \u0026#34;phase\u0026#34;: \u0026#34;Active\u0026#34; } }, { \u0026#34;metadata\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;kube-system\u0026#34;, \u0026#34;selfLink\u0026#34;: \u0026#34;/api/v1/namespaces/kube-system\u0026#34;, \u0026#34;uid\u0026#34;: \u0026#34;...\u0026#34;, \u0026#34;resourceVersion\u0026#34;: \u0026#34;166\u0026#34;, \u0026#34;creationTimestamp\u0026#34;: \u0026#34;2019-12-16T12:29:50Z\u0026#34;, \u0026#34;annotations\u0026#34;: { \u0026#34;kubectl.kubernetes.io/last-applied-configuration\u0026#34;: \u0026#34;{\\\u0026#34;apiVersion\\\u0026#34;:\\\u0026#34;v1\\\u0026#34;,\\\u0026#34;kind\\\u0026#34;:\\\u0026#34;Namespace\\\u0026#34;,\\\u0026#34;metadata\\\u0026#34;:{\\\u0026#34;annotations\\\u0026#34;:{},\\\u0026#34;name\\\u0026#34;:\\\u0026#34;kube-system\\\u0026#34;,\\\u0026#34;namespace\\\u0026#34;:\\\u0026#34;\\\u0026#34;}}\\n\u0026#34; } }, \u0026#34;spec\u0026#34;: { \u0026#34;finalizers\u0026#34;: [ \u0026#34;kubernetes\u0026#34; ] }, \u0026#34;status\u0026#34;: { \u0026#34;phase\u0026#34;: \u0026#34;Active\u0026#34; } } ] } 上の例ではcurlで認証情報を渡すのが面倒なのでproxyを使用したが,\n本来kubectlがkube-apiserverと通信するために必要な認証情報はkubeconfig(~/.kube/config)に記述する.\n各種サービスを使用してクラスタを構築した場合はデフォルトで記述されていることが多い.\n# kubeconfigを確認 $ cat ~/.kube/config apiVersion: v1 clusters: - cluster: certificate-authority-data: ... server: https://34.83.155.176 name: gke_uzimihsr-01_us-west1-a_uzimihsr-k8s-perfect-guide - cluster: certificate-authority: /Users/uzimihsr/.minikube/ca.crt server: https://192.168.99.110:8443 name: minikube contexts: - context: cluster: gke_uzimihsr-01_us-west1-a_uzimihsr-k8s-perfect-guide user: gke_uzimihsr-01_us-west1-a_uzimihsr-k8s-perfect-guide name: gke_uzimihsr-01_us-west1-a_uzimihsr-k8s-perfect-guide - context: cluster: minikube user: minikube name: minikube current-context: gke_uzimihsr-01_us-west1-a_uzimihsr-k8s-perfect-guide kind: Config preferences: {} users: - name: gke_uzimihsr-01_us-west1-a_uzimihsr-k8s-perfect-guide user: auth-provider: config: access-token: ... cmd-args: config config-helper --format=json cmd-path: /Users/uzimihsr/google-cloud-sdk/bin/gcloud expiry: 2019-12-16T14:59:54Z expiry-key: \u0026#39;{.credential.token_expiry}\u0026#39; token-key: \u0026#39;{.credential.access_token}\u0026#39; name: gcp - name: minikube user: client-certificate: /Users/uzimihsr/.minikube/client.crt client-key: /Users/uzimihsr/.minikube/client.key kubectlではkubeconfigのContextを切り替えることで異なるクラスタへ接続することができる.\n# Contextの一覧を表示 # GKE用のContextを使用している $ kubectl config get-contexts CURRENT NAME CLUSTER AUTHINFO NAMESPACE * gke_uzimihsr-01_us-west1-a_uzimihsr-k8s-perfect-guide gke_uzimihsr-01_us-west1-a_uzimihsr-k8s-perfect-guide gke_uzimihsr-01_us-west1-a_uzimihsr-k8s-perfect-guide minikube minikube minikube # 使用するContextを変更 $ kubectl config use-context minikube Switched to context \u0026#34;minikube\u0026#34;. # 現在のContextを確認 $ kubectl config current-context minikube kubectlとマニフェストファイルを使ってリソースの操作をすることができる.\n# リソース(Pod)の作成 $ kubectl apply -f sample-pod.yaml pod/sample-pod created # Podの一覧表示 $ kubectl get pods NAME READY STATUS RESTARTS AGE sample-pod 1/1 Running 0 18s # リソースの削除 $ kubectl delete -f sample-pod.yaml pod \u0026#34;sample-pod\u0026#34; deleted sample-pod.yaml\nリソースにKey-Value形式のラベルをつけることで, 管理がしやすくなる.\nReplicaSetやServiceではこのラベルを使用してPodの管理を行っている.\n# Pod(sample-label)はlabel1(Key)=val1(Value)とlabel2=val2の2つのラベルを持つ $ kubectl describe pod sample-label ... Labels: label1=val1 label2=val2 ... # Podの一覧表示 $ kubectl get pods NAME READY STATUS RESTARTS AGE sample-label 1/1 Running 0 59s sample-pod 1/1 Running 0 29s # ラベル(label1=val1)で絞り込む $ kubectl get pods -l label1=val1 NAME READY STATUS RESTARTS AGE sample-label 1/1 Running 0 5m55s # ラベルのValueを表示させる $ kubectl get pods -L label1 -L label2 NAME READY STATUS RESTARTS AGE LABEL1 LABEL2 sample-label 1/1 Running 0 7m41s val1 val2 sample-pod 1/1 Running 0 7m11s sample-label.yaml\nkubectl getはオプションを付けることで出力形式を指定することができる.\n# 詳細な情報を付与して一覧表示 $ kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES sample-label 1/1 Running 0 11m 10.4.2.4 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-0dgf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; sample-pod 1/1 Running 0 10m 10.4.2.5 gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-0dgf \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; # YAML形式でPod(sample-pod)の情報を出力 $ kubectl get pod sample-pod -o yaml apiVersion: v1 kind: Pod ... status: ... hostIP: 10.138.0.5 ... ... # 表示項目を指定して一覧表示 # Podのmetadata.nameの値をNAME, status.hostIPの値をNodeIPとして表示 $ kubectl get pods -o custom-columns=\u0026#34;NAME:{.metadata.name},NodeIP:{.status.hostIP}\u0026#34; NAME NodeIP sample-label 10.138.0.5 sample-pod 10.138.0.5 kubectl topでCPUやメモリの使用量を確認できる.\n# NodeのCPU, メモリ使用量を確認 $ kubectl top nodes NAME CPU(cores) CPU% MEMORY(bytes) MEMORY% gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-0dgf 51m 5% 674Mi 25% gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-n2mf 117m 12% 786Mi 29% gke-uzimihsr-k8s-perfect-default-pool-e0d8f087-wk64 37m 3% 651Mi 24% # PodのCPU, メモリ使用量をコンテナ情報つきで確認 $ kubectl top pods --containers POD NAME CPU(cores) MEMORY(bytes) sample-label nginx-container 0m 1Mi sample-pod nginx-container 0m 1Mi kubectl execでPod上でコマンドを実行することができる.\n# Podのコンテナでshを起動 $ kubectl exec -it sample-pod -c nginx-container /bin/sh # Ctrl+Dまたはexitコマンドで終了 # コマンドに引数がある場合は--の後に指定する $ kubectl exec -it sample-pod -c nginx-container -- /bin/sh -c \u0026#34;ls --all --classify | grep media\u0026#34; media/ kubectl logsでPodのログが確認できる.\n# コンテナを指定してPodのログを確認 $ kubectl logs sample-pod -c nginx-container ... # 全コンテナのログを垂れ流す $ kubectl logs -f sample-pod --all-containers ... # 期間を指定して出力 # 1時間前から10件分のログをタイムスタンプ付きで表示 $ kubectl logs --since=1h --tail=10 --timestamps=true sample-pod ... kubectl cpでPodとローカルマシンでファイルのコピーができる.\n# Podの/etc/hostnameをローカルにコピー $ kubectl cp sample-pod:/etc/hostname ./ $ cat ./hostname sample-pod # ローカルのファイルをPodの/tmp/newfileにコピー $ kubectl cp ./hostname sample-pod:/tmp/newfile $ kubectl exec -it sample-pod -- /bin/sh -c \u0026#34;cat /tmp/newfile\u0026#34; sample-pod kubectl port-forwardでポート転送ができる.\n# localhostの8888番ポートをPodの80番ポートに転送 $ kubectl port-forward sample-pod 8888:80 Forwarding from 127.0.0.1:8888 -\u0026gt; 80 Forwarding from [::1]:8888 -\u0026gt; 80 # 確認できたらCtrl+Cで終了 # 別ターミナルで疎通確認 $ curl -I localhost:8888 HTTP/1.1 200 OK Server: nginx/1.12.2 ... おまけ ","date":"2019-12-16T21:26:15+09:00","image":"/post/2019-12-16-kubernetes-guide-chap4/2019-12-16-sotochan.jpg","permalink":"/post/2019-12-16-kubernetes-guide-chap4/","title":"Kubernetes完全に理解したい 4章"},{"content":"YAML書いてみる なんだかんだで一度もリソースを手で書いたことが無いので, 自分でつくってみる.\nやったことのまとめ 任意のコマンドが実行できるDocker imageを作成してKubernetesのJobを実行してみた Jobとは 使い切りのPodを実行し, 設定した回数ぶん終了したら完了となるリソース Podが処理を行った後停止することが前提 Podを監視し, 正常終了の回数が設定値を満たすまで再起動等の処理を行う 一回の処理を確実に行いたい場合やバッチ的な処理に向いている 参考 : https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/\nやったこと Docker imageの作成 マニフェストファイルの作成 Jobの実行 Docker imageの作成 まずはDocker imageをつくる.\nDockerfileの書き方はだいたいわかっているつもり\u0026hellip;\n下記のDockerfileを適当なディレクトリで作成する.\n特に何かの処理をするわけではなく, ただ alpine のimageをベースにして適当な環境変数等を設定しただけ.\nENTRYPOINTに /bin/sh を指定することで引数で任意のコマンドを渡せるようにする.\nDockerfile\n# ベースイメージはalpine FROM alpine:3.10.3 # 管理者の名前 MAINTAINER uzimihsr-01 # ディレクトリを変更 WORKDIR /uzimihsr/workdir # 環境変数を設定 ENV AUTHOR uzimihsr ENV NUMBER 777 # エントリーポイントはsh ENTRYPOINT [\u0026#34;/bin/sh\u0026#34;] # デフォルトでは環境変数AUTHORを表示する CMD [\u0026#34;-c\u0026#34;, \u0026#34;echo ${AUTHOR}\u0026#34;] imageをビルドしてDocker Hubにアップロードする.\n# Dockerfileがあるところで作業 $ cd path/to/Dockerfile $ ls Dockerfile # uzimihsr(Docker HubのID)とrepository, tagを指定してビルド $ docker image build -t uzimihsr/uzimihsr-sample-image:0.1 . Sending build context to Docker daemon 58.37kB Step 1/7 : FROM alpine:3.10.3 ---\u0026gt; 965ea09ff2eb Step 2/7 : MAINTAINER uzimihsr-01 ---\u0026gt; Using cache ---\u0026gt; be67ea5322ce Step 3/7 : WORKDIR /uzimihsr/workdir ---\u0026gt; Using cache ---\u0026gt; d953d022ec9e Step 4/7 : ENV AUTHOR uzimihsr ---\u0026gt; Using cache ---\u0026gt; f2e3624824fd Step 5/7 : ENV NUMBER 777 ---\u0026gt; Using cache ---\u0026gt; c5ccb27bba82 Step 6/7 : ENTRYPOINT [\u0026#34;/bin/sh\u0026#34;] ---\u0026gt; Using cache ---\u0026gt; 42d33fa1c9ab Step 7/7 : CMD [\u0026#34;-c\u0026#34;, \u0026#34;echo ${AUTHOR}\u0026#34;] ---\u0026gt; Using cache ---\u0026gt; ec0ff7287ea6 Successfully built ec0ff7287ea6 Successfully tagged uzimihsr/uzimihsr-sample-image:0.1 # imageをDocker Hubにpush $ docker image push uzimihsr/uzimihsr-sample-image:0.1 The push refers to repository [docker.io/uzimihsr/uzimihsr-sample-image] 47cb607f05a0: Pushed 77cae8ab23bf: Mounted from library/alpine 0.1: digest: sha256:eab7f925c7b33c5604196d30a9daa3e2d9c763166543069b8f31676a74003702 size: 735 https://hub.docker.com/r/uzimihsr/uzimihsr-sample-image\n試しにローカルにある uzimihsr/uzimihsr-sample-image:0.1 を削除した状態でコンテナを実行してみる.\n# Docker Hubにあるimageを指定してコンテナを実行 # Dockerfileで指定した通り$AUTHORが表示される $ docker container run uzimihsr/uzimihsr-sample-image:0.1 Unable to find image \u0026#39;uzimihsr/uzimihsr-sample-image:0.1\u0026#39; locally 0.1: Pulling from uzimihsr/uzimihsr-sample-image Digest: sha256:eab7f925c7b33c5604196d30a9daa3e2d9c763166543069b8f31676a74003702 Status: Downloaded newer image for uzimihsr/uzimihsr-sample-image:0.1 uzimihsr 動作確認はOK.\nマニフェストファイルの作成 いよいよYAMLを書いていく.\n基本的にはapiVersion, kind, metadata, specの項目を埋めていけばいいはず.\napiVersion: このマニフェストを読むKubernetes APIのバージョンを指定 今回はJobを使うのでbatch/v1を指定する https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.16/#job-v1-batch kind: 今回定義するリソースの種類 Jobを定義する metadata: リソースを識別するためのメタ情報 Jobに名前をつける https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.16/#objectmeta-v1-meta spec: このリソースのパラメータや設定 Jobのパラメータや設定を定義する https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.16/#jobspec-v1-batch job.yaml\napiVersion: batch/v1 # APIのバージョン kind: Job # リソースの種類 metadata: # メタ情報 name: uzimihsr-echo-number # リソース名 spec: # Jobの定義 template: # Pod情報 spec: # Jobで作成されるPodの定義 containers: # Podに含まれるコンテナ - name: echo-number # コンテナ名 image: uzimihsr/uzimihsr-sample-image:0.1 # 使用するimage args: [\u0026#34;-c\u0026#34;, \u0026#34;echo ${NUMBER}\u0026#34;] # imageのCMDを上書きする restartPolicy: Never # Podが失敗した場合の挙動 backoffLimit: 1 # 再トライ回数の制限 ちょっとコメントを多めに入れたのでごちゃごちゃしているが, やっていることは非常にシンプル.\nspecにはJobで実行されるPod内のコンテナとその設定を書いている.\n今回は先程作ったimageの実行時引数をargsで指定するとDockerfileで指定したCMDを上書きしてくれるので,\nimageが持つ環境変数 NUMBER を表示するように指定している.\nJobではPod失敗時の挙動を指定するrestartPolicyにNever(新しいPodを作成)かOnFailure(NodeにPodを残したままコンテナを再実行)が指定できるが, Neverにしておくのが無難そう.\nJobの実行 いよいよマニフェストを実行する.\nkubectlでリソースを作成して, その後の挙動を確認する.\n# リソースの作成 $ kubectl apply -f job.yaml job.batch/uzimihsr-echo-number created # Jobの一覧を確認 $ kubectl get jobs NAME COMPLETIONS DURATION AGE uzimihsr-echo-number 1/1 5s 5s # Podの一覧を確認 # JobによってPodが起動している $ kubectl get pods NAME READY STATUS RESTARTS AGE uzimihsr-echo-number-vpbg7 0/1 Completed 0 14s # Podのログを確認 # imageの元々の挙動ではなくJobで定義した処理が実行されている $ kubectl logs uzimihsr-echo-number-vpbg7 777 # Podが作成された際のeventを確認 $ kubectl get events LAST SEEN TYPE REASON OBJECT MESSAGE \u0026lt;unknown\u0026gt; Normal Scheduled pod/uzimihsr-echo-number-vpbg7 Successfully assigned default/uzimihsr-echo-number-vpbg7 to minikube 3m Normal Pulled pod/uzimihsr-echo-number-vpbg7 Container image \u0026#34;uzimihsr/uzimihsr-sample-image:0.1\u0026#34; already present on machine 3m Normal Created pod/uzimihsr-echo-number-vpbg7 Created container echo-number 3m Normal Started pod/uzimihsr-echo-number-vpbg7 Started container echo-number 3m1s Normal SuccessfulCreate job/uzimihsr-echo-number Created pod: uzimihsr-echo-number-vpbg7 # Jobの情報を確認 # Job定義の他, Podのステータス監視情報などが表示される $ kubectl describe job uzimihsr-echo-number Name: uzimihsr-echo-number Namespace: default Selector: controller-uid=77d09e29-33f9-4175-a8e4-7b903ee44a9c Labels: controller-uid=77d09e29-33f9-4175-a8e4-7b903ee44a9c job-name=uzimihsr-echo-number Annotations: kubectl.kubernetes.io/last-applied-configuration={\u0026#34;apiVersion\u0026#34;:\u0026#34;batch/v1\u0026#34;,\u0026#34;kind\u0026#34;:\u0026#34;Job\u0026#34;,\u0026#34;metadata\u0026#34;:{\u0026#34;annotations\u0026#34;:{},\u0026#34;name\u0026#34;:\u0026#34;uzimihsr-echo-number\u0026#34;,\u0026#34;namespace\u0026#34;:\u0026#34;default\u0026#34;},\u0026#34;spec\u0026#34;:{\u0026#34;backoffLimit\u0026#34;:1,\u0026#34;templ...\u0026#34; Parallelism: 1 Completions: 1 Start Time: Sun, 15 Dec 2019 20:29:29 +0900 Pods Statuses: 0 Running / 1 Succeeded / 0 Failed Pod Template: Labels: controller-uid=77d09e29-33f9-4175-a8e4-7b903ee44a9c job-name=uzimihsr-echo-number Containers: echo-number: Image: uzimihsr/uzimihsr-sample-image:0.1 Port: \u0026lt;none\u0026gt; Host Port: \u0026lt;none\u0026gt; Args: -c echo ${NUMBER} Environment: \u0026lt;none\u0026gt; Mounts: \u0026lt;none\u0026gt; Volumes: \u0026lt;none\u0026gt; Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal SuccessfulCreate 3m job-controller Created pod: uzimihsr-echo-number-vpbg7 # Podの情報を確認 $ kubectl describe pod uzimihsr-echo-number-vpbg7 Name: uzimihsr-echo-number-vpbg7 Namespace: default Priority: 0 PriorityClassName: \u0026lt;none\u0026gt; Node: minikube/192.168.99.110 Start Time: Sun, 15 Dec 2019 20:29:29 +0900 Labels: controller-uid=77d09e29-33f9-4175-a8e4-7b903ee44a9c job-name=uzimihsr-echo-number Annotations: \u0026lt;none\u0026gt; Status: Succeeded IP: 172.17.0.6 Controlled By: Job/uzimihsr-echo-number Containers: echo-number: Container ID: docker://d3cb3192f650cb916995ad25d533fb4f9edcd42a800cbf41bf2bbc0221ef204c Image: uzimihsr/uzimihsr-sample-image:0.1 Image ID: docker-pullable://uzimihsr/uzimihsr-sample-image@sha256:eab7f925c7b33c5604196d30a9daa3e2d9c763166543069b8f31676a74003702 Port: \u0026lt;none\u0026gt; Host Port: \u0026lt;none\u0026gt; Args: -c echo ${NUMBER} State: Terminated Reason: Completed Exit Code: 0 Started: Sun, 15 Dec 2019 20:29:30 +0900 Finished: Sun, 15 Dec 2019 20:29:30 +0900 Ready: False Restart Count: 0 Environment: \u0026lt;none\u0026gt; Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-smq54 (ro) Conditions: Type Status Initialized True Ready False ContainersReady False PodScheduled True Volumes: default-token-smq54: Type: Secret (a volume populated by a Secret) SecretName: default-token-smq54 Optional: false QoS Class: BestEffort Node-Selectors: \u0026lt;none\u0026gt; Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled \u0026lt;unknown\u0026gt; default-scheduler Successfully assigned default/uzimihsr-echo-number-vpbg7 to minikube Normal Pulled 10m kubelet, minikube Container image \u0026#34;uzimihsr/uzimihsr-sample-image:0.1\u0026#34; already present on machine Normal Created 10m kubelet, minikube Created container echo-number Normal Started 10m kubelet, minikube Started container echo-number できた.\neventの順番を見るとJobが作成された際の各コンポーネントの動きが推測できておもしろい.\n多分以下の流れ.\nkubectlから指示を受けたkube-apiserverがJobの情報をetcdに書き込み kube-controller-managerにJobのControllerが作成される JobのControllerがkube-apiserverに指示を出してPodの情報をetcdに書き込み kube-schedulerによってPodがNodeに割り当てられる NodeのDockerがPodに使うimageを探してコンテナを作成+起動, これをkubeletが報告 Podが作成されたことをJobのControllerが検知 Podが完了したときにそれを示すeventが作成されないのはなんでだろう\u0026hellip;\nたぶん意図があるはずなのでそのうち調べたい.\nおわり Kubernetesのマニフェストを初めて手書きして実行までやってみた.\nやっぱり先にコンポーネントの役割を理解しておいたほうがクラスタ全体の挙動がなんとなくわかって良い気がする.\n(間違ってたらかなしい)\nおまけ ","date":"2019-12-09T22:40:21+09:00","image":"/post/2019-12-09-pod-args/2019-12-09-sotochan.jpg","permalink":"/post/2019-12-09-pod-args/","title":"KubernetesのJobを試してみる"},{"content":"もうちょっとくわしくなりたい これまでKubernetesクラスタについてちょっとずつ勉強してMasterガーnodeガーって自分なりに解釈してきたけど,\nそのMasterとNodeがどんなコンポーネントで構成されているか気になったのでまとめた.\n読んだもの Kubernetes完全ガイド 19章 Kubernetesのアーキテクチャを知る Kubernetes Components コンポーネントの構成 Kubernetesクラスタを構成するコンポーネントの構成はこんな感じ.\nほぼすべてのコンポーネントはMasterのkube-apiserverとつながっている.\nIcons made by Smashicons from www.flaticon.com Masterコンポーネント etcd kube-apiserver kube-scheduler kube-controller-manager cloud-controller-manager Nodeコンポーネント kubelet kube-proxy コンテナランタイム(Docker) アドオン DNS Web UI(ダッシュボード) コンテナリソース監視 クラスターレベルログ Masterコンポーネント クラスタの制御(スケジューリングなど)を行う クラスタイベントの検出/応答を行う クラスタ内のどのマシンでも実行可能だが, 基本的に同じマシンで実行する シンプルさを保つため全てを1つにまとめる このマシンではユーザーのコンテナは実行しない etcd 分散型KVS(Key-Value Store) 冗長化のためにetcdクラスタを組んで運用する 分散合意アルゴリズム(Raft)を用いるため奇数台で運用する クラスタの全ての情報を保存する場所 kube-apiserver Kubernetes APIを提供するコンポーネント kubectlではこれにリクエストを送ることでリソースの操作を行う kube-scheduler, kube-controller-manager, kubelet等はこれにリクエストを送ることで処理をしている 受け取ったリクエストの情報はetcdに保存される ロードバランサの下に複数台並べて運用する kube-scheduler etcdに新たに登録されたリソース(Pod)をNodeに割り当てるコンポーネント kube-apiserverにリクエストを送ってNodeが未定のPod情報を取得 各Nodeのステータス等を考慮して割り当てをおこなう 割り当てが決定したらkube-apiserverにリクエストを送ってPodをスケジューリングする 冗長化のため, 複数台で運用する kube-controller-manager 各種Controllerを実行するコンポーネント 各種リソースの状態を監視して必要な操作を決定 操作が決定したらkube-apiserverにリクエストを送る 冗長化のため, 複数台で運用する cloud-controller-manager クラウドサービス固有の制御を担当するコンポーネント クラウドプロバイダーと対話を行う クラウド固有の必要な操作を行うためのControllerを扱う kube-controller-manegerとControllerがかぶらないようにする クラウドサービスを利用しない場合は特に気にする必要なし Nodeコンポーネント Nodeで稼働中のPodの管理を行う Kubernetesの実行環境を提供する kubelet 実際にコンテナの管理を行うコンポーネント Podの動作を保証する Kubernetesが作成したコンテナのみを管理する kube-apiserverにリクエストを送って自身のNodeにスケジュールされたPodを確認, 起動する コンテナランタイムと連携する 各Node上で動作する kube-proxy 外部からのトラフィックを正常に転送するためのコンポーネント Service宛のトラフィックをPodに転送する 各Nodeで動作 コンテナランタイム(Docker) コンテナの実行を担当するソフトウェア ユーザーのコンテナはここで実行 Docker以外のランタイムも利用可能 containerd, cri-o, rktletなど アドオン クラスタの機能を実装する Deployment, DaemonSetなどのリソースを使用 kube-systemというnamespaceに置かれている 以下は代表的なアドオン DNS(CoreDNS) ほぼ必須のアドオン クラスタ内のDNSを管理 PodとServiceの名前解決を行う Web UI(ダッシュボード) グラフィカルな操作が可能なUI リソース情報やログの閲覧が可能 コンテナリソース監視 コンテナのメトリクスをDBに保存 メトリクスを可視化するためのUIを提供 Prometheusなどが利用可能 クラスターレベルログ コンテナのログを様々な方法で取得する 各Nodeでログを取得してバックエンドに送るためのロギングエージェントPodを実行する アプリコンテナのログを取得して標準出力するSidecarコンテナを各Podに追加する 各コンテナのログを直接バックエンドに出力する おわり 各種コンポーネントの役割についてなんとなくわかった気になった.\nMasterではkube-apiserverとetcdが, Nodeではkubeletが一番重要なんだと思う.\nアドオンに関してはDNSが重要なのはわかったけど他はあんまりわかんなかったので必要に応じて勉強したい.\n次はKubernetes完全ガイドの内容とかを少しずつまとめていきたい.\n","date":"2019-11-25T21:20:53+09:00","permalink":"/post/2019-11-25-kubernetes-components/","title":"Kubernetesの主要なコンポーネントについてまとめる"},{"content":"Googleサイコー Kubernetesを完全に理解したくて買ったKubernetes完全ガイドをざっと読んだらGKE使ってるっぽかったので, 元々興味もあったし事前準備としてGKEを試してみた.\nやったことのまとめ Google Kubernetes Engine(GKE)の無料トライアルを始めた GKEでKubernetesクラスタを構築してアプリをデプロイ, 公開した後クラスタを削除した GKEってなに? GCPが提供してる超便利なマネージドKubernetesサービス.\nすっげえ簡単にクラスタの構築, 管理ができるらしい.\nやったこと GCPの利用を開始する プロジェクトの作成 Google Cloud SDKのインストール Kubernetes Engineクラスタの作成 アプリのデプロイ Kubernetes Engineクラスタの削除 動作環境 masOS Mojave 10.14 GCPの利用を開始する https://cloud.google.com/kubernetes-engine/ にアクセス.\n無料トライアルを押す.\n利用規約の承諾とか住所とか支払い方法を入力して無料トライアルを開始(スクショには映ってないけどお支払い方法の下にある)を押す.\nGCPコンソールのGKEの画面に遷移して, Kubernetes Engine API(たぶんGKEのAPI)の有効化が始まる.\nちょっと待つと準備が完了して, デフォルトで作成されたプロジェクト(My First Project)でKubernetesクラスタが作れるようになる.\nこのままこのプロジェクトでクラスタ構築して始められそうなんだけど,\n初心者なので今回は敢えてクイックスタートの手順で新たにプロジェクトを作成してCLIからクラスタを構築してみる.\nプロジェクトの作成 再度GKEの画面を開く.\nヘッダーからプロジェクトの選択-\u0026gt;新しいプロジェクトと進む.\nプロジェクト作成画面が開くので, プロジェクト名(uzimihsr-01)だけ入力して作成を押す.\nまたまたGKEの画面に遷移して先程と同様にKubernetes Engine APIの有効化が始まり, 少し待つと有効になる.\n無料トライアルの開始時とやってることは一緒.\nGoogle Cloud SDKのインストール macOS 用のクイックスタートに従ってGoogle Cloud SDKとgcloudをインストールする.\nまずはシステムにPython 2.7がインストールされていることを確認.\n自分は普段システム(/usr/bin/python)じゃなくてHomebrewで入れたPython 2.7を使っているけど,\nとにかくバージョン2.7のPythonにパスが通ってればいいみたい.\n# pythonのバージョン確認 $ which python /usr/local/bin/python $ python -V Python 2.7.16 macOS 64bit用のgoogle-cloud-sdk-245.0.0-darwin-x86_64.tar.gzをダウンロード.\nホームディレクトリに移動して展開しておく.\n# ホームディレクトリにgoogle-cloud-sdkが配置されていることを確認 $ pwd /Users/\u0026lt;ユーザ名\u0026gt; $ ls ... google-cloud-sdk ... インストールスクリプトinstall.shを実行してgcloudコマンドをインストールする.\n途中でデータ収集に協力するか, とかCLIのPATHを通すかとか聞かれるので前者はNo, 後者はYesと答える.\n# インストールスクリプトの実行 $ ./google-cloud-sdk/install.sh Welcome to the Google Cloud SDK! To help improve the quality of this product, we collect anonymized usage data and anonymized stacktraces when crashes are encountered; additional information is available at \u0026lt;https://cloud.google.com/sdk/usage-statistics\u0026gt;. You may choose to opt out of this collection now (by choosing \u0026#39;N\u0026#39; at the below prompt), or at any time in the future by running the following command: gcloud config set disable_usage_reporting true Do you want to help improve the Google Cloud SDK (Y/n)? n Your current Cloud SDK version is: 245.0.0 The latest available version is: 272.0.0 ┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Components │ ├──────────────────┬──────────────────────────────────────────────────────┬──────────────────────────┬──────────┤ │ Status │ Name │ ID │ Size │ ├──────────────────┼──────────────────────────────────────────────────────┼──────────────────────────┼──────────┤ │ Update Available │ BigQuery Command Line Tool │ bq │ \u0026lt; 1 MiB │ │ Update Available │ Cloud SDK Core Libraries │ core │ 12.5 MiB │ │ Update Available │ Cloud Storage Command Line Tool │ gsutil │ 3.6 MiB │ │ Not Installed │ App Engine Go Extensions │ app-engine-go │ 4.8 MiB │ │ Not Installed │ Appctl │ appctl │ 18.6 MiB │ │ Not Installed │ Cloud Bigtable Command Line Tool │ cbt │ 7.3 MiB │ │ Not Installed │ Cloud Bigtable Emulator │ bigtable │ 6.6 MiB │ │ Not Installed │ Cloud Datalab Command Line Tool │ datalab │ \u0026lt; 1 MiB │ │ Not Installed │ Cloud Datastore Emulator │ cloud-datastore-emulator │ 18.4 MiB │ │ Not Installed │ Cloud Firestore Emulator │ cloud-firestore-emulator │ 40.0 MiB │ │ Not Installed │ Cloud Pub/Sub Emulator │ pubsub-emulator │ 34.9 MiB │ │ Not Installed │ Cloud SQL Proxy │ cloud_sql_proxy │ 3.7 MiB │ │ Not Installed │ Emulator Reverse Proxy │ emulator-reverse-proxy │ 14.5 MiB │ │ Not Installed │ Google Cloud Build Local Builder │ cloud-build-local │ 5.9 MiB │ │ Not Installed │ Google Container Registry s Docker credential helper │ docker-credential-gcr │ 1.8 MiB │ │ Not Installed │ Skaffold │ skaffold │ 44.0 MiB │ │ Not Installed │ gcloud Alpha Commands │ alpha │ \u0026lt; 1 MiB │ │ Not Installed │ gcloud Beta Commands │ beta │ \u0026lt; 1 MiB │ │ Not Installed │ gcloud app Java Extensions │ app-engine-java │ 62.0 MiB │ │ Not Installed │ gcloud app PHP Extensions │ app-engine-php │ 21.9 MiB │ │ Not Installed │ gcloud app Python Extensions │ app-engine-python │ 6.0 MiB │ │ Not Installed │ gcloud app Python Extensions (Extra Libraries) │ app-engine-python-extras │ 27.1 MiB │ │ Not Installed │ kubectl │ kubectl │ \u0026lt; 1 MiB │ └──────────────────┴──────────────────────────────────────────────────────┴──────────────────────────┴──────────┘ To install or remove components at your current SDK version [245.0.0], run: $ gcloud components install COMPONENT_ID $ gcloud components remove COMPONENT_ID To update your SDK installation to the latest version [272.0.0], run: $ gcloud components update To take a quick anonymous survey, run: $ gcloud alpha survey Modify profile to update your $PATH and enable shell command completion? Do you want to continue (Y/n)? y The Google Cloud SDK installer will now prompt you to update an rc file to bring the Google Cloud CLIs into your environment. Enter a path to an rc file to update, or leave blank to use [/Users/\u0026lt;ユーザ名\u0026gt;/.zshrc]: Backing up [/Users/\u0026lt;ユーザ名\u0026gt;/.zshrc] to [/Users/\u0026lt;ユーザ名\u0026gt;/.zshrc.backup]. [/Users/\u0026lt;ユーザ名\u0026gt;/.zshrc] has been updated. ==\u0026gt; Start a new shell for the changes to take effect. For more information on how to get started, please visit: https://cloud.google.com/sdk/docs/quickstarts # シェルを再起動 $ exec $SHELL -l # gcloudコマンドが使えるか確認 $ gcloud --version Google Cloud SDK 245.0.0 bq 2.0.43 core 2019.05.03 gsutil 4.38 kubectl 2019.05.03 Updates are available for some Cloud SDK components. To install them, please run: $ gcloud components update これでgcloudコマンドが使えるようになった.\n次にSDKの初期化を行う.\nログインするか聞かれるのでYesと答えるとブラウザが開かれる.\n# SDKの初期化 $ gcloud init Welcome! This command will take you through the configuration of gcloud. Your current configuration has been set to: [default] You can skip diagnostics next time by using the following flag: gcloud init --skip-diagnostics Network diagnostic detects and fixes local network connection issues. Checking network connection...done. Reachability Check passed. Network diagnostic passed (1/1 checks passed). You must log in to continue. Would you like to log in (Y/n)? y Your browser has been opened to visit: https://accounts.google.com/o/oauth2/auth?redirect_uri=... # 勝手にブラウザが開く Googleのログイン画面が表示されるので, 内容を読んで権限を許可する.\n認証に成功すると画面遷移するので, ブラウザを閉じてターミナルに戻る.\nすると先程のダイアログの続きが表示されているので指示に従ってすすめる.\n途中でどのプロジェクトを使うか聞かれるので, 先程作成したプロジェクト(uzimihsr-01)の番号を選択する.\nRegionとZoneの設定は後でもできるので今はNoにしておく.\n# さっきのダイアログにつづきが表示されている $ gcloud init Welcome! This command will take you through the configuration of gcloud. Your current configuration has been set to: [default] You can skip diagnostics next time by using the following flag: gcloud init --skip-diagnostics Network diagnostic detects and fixes local network connection issues. Checking network connection...done. Reachability Check passed. Network diagnostic passed (1/1 checks passed). You must log in to continue. Would you like to log in (Y/n)? y Your browser has been opened to visit: https://accounts.google.com/o/oauth2/auth?redirect_uri=... Updates are available for some Cloud SDK components. To install them, please run: $ gcloud components update You are logged in as: [xxxxxxxx@gmail.com]. Pick cloud project to use: [1] nifty-catfish-259613 [2] uzimihsr-01 [3] Create a new project Please enter numeric choice or text value (must exactly match list item): 2 Your current project has been set to: [uzimihsr-01]. Do you want to configure a default Compute Region and Zone? (Y/n)? n Created a default .boto configuration file at [/Users/\u0026lt;ユーザー名\u0026gt;/.boto]. See this file and [https://cloud.google.com/storage/docs/gsutil/commands/config] for more information about configuring Google Cloud Storage. Your Google Cloud SDK is configured and ready to use! * Commands that require authentication will use xxxxxxxx@gmail.com by default * Commands will reference project `uzimihsr-01` by default Run `gcloud help config` to learn how to change individual settings This gcloud configuration is called [default]. You can create additional configurations if you work with multiple accounts and/or projects. Run `gcloud topic configurations` to learn more. Some things to try next: * Run `gcloud --help` to see the Cloud Platform services you can interact with. And run `gcloud help COMMAND` to get help on any gcloud command. * Run `gcloud topic --help` to learn about advanced features of the SDK like arg files and output formatting これでSDKの初期化は完了.\n次にkubectlをインストールする.\nDocker DesktopとかMinikubeを使ったときにHomebrewで入れてあるはずだけど, 一応再インストールしてみる.\n# kubectlのインストール $ gcloud components install kubectl Your current Cloud SDK version is: 245.0.0 Installing components from version: 245.0.0 ┌─────────────────────────────────────────────────────────────────────┐ │ These components will be installed. │ ├─────────────────────┬────────────────────────┬──────────────────────┤ │ Name │ Version │ Size │ ├─────────────────────┼────────────────────────┼──────────────────────┤ │ kubectl │ 2019.05.03 │ \u0026lt; 1 MiB │ │ kubectl │ 1.11.9 │ 65.3 MiB │ └─────────────────────┴────────────────────────┴──────────────────────┘ For the latest full release notes, please visit: https://cloud.google.com/sdk/release_notes Do you want to continue (Y/n)? y ╔════════════════════════════════════════════════════════════╗ ╠═ Creating update staging area ═╣ ╠════════════════════════════════════════════════════════════╣ ╠═ Installing: kubectl ═╣ ╠════════════════════════════════════════════════════════════╣ ╠═ Installing: kubectl ═╣ ╠════════════════════════════════════════════════════════════╣ ╠═ Creating backup and activating new installation ═╣ ╚════════════════════════════════════════════════════════════╝ Performing post processing steps...done. Update done! WARNING: There are older versions of Google Cloud Platform tools on your system PATH. Please remove the following to avoid accidentally invoking these old tools: /usr/local/Cellar/kubernetes-cli/1.16.2/bin/kubectl # 元々入ってたkubectl(/usr/local/bin/kubectl)は念の為削除する $ which kubectl /usr/local/bin/kubectl $ rm /usr/local/Cellar/kubernetes-cli/1.16.2/bin/kubectl remove /usr/local/Cellar/kubernetes-cli/1.16.2/bin/kubectl? y $ exec $SHELL -l $ which kubectl /Users/username/google-cloud-sdk/bin/kubectl これでSDKのインストールは完了.\nKubernetes Engineクラスタの作成 次にgcloudで使用するデフォルトのプロジェクト, Zoneの設定をする.\n先程gcloud initしたときとほとんど同じことをやっている.\n# デフォルトプロジェクトをuzimihsr-01に設定 $ gcloud config set project uzimihsr-01 Updated property [core/project]. # コンピューティングゾーンをus-west1-aに設定 $ gcloud config set compute/zone us-west1-a Updated property [compute/zone]. いよいよKubernetesクラスタを作成する.\n作成自体はコマンド1行でできる. すごい.\n# k8s-uzimihsrという名前でクラスタを作成 $ gcloud container clusters create k8s-uzimihsr WARNING: In June 2019, node auto-upgrade will be enabled by default for newly created clusters and node pools. To disable it, use the `--no-enable-autoupgrade` flag. WARNING: Starting in 1.12, new clusters will have basic authentication disabled by default. Basic authentication can be enabled (or disabled) manually using the `--[no-]enable-basic-auth` flag. WARNING: Starting in 1.12, new clusters will not have a client certificate issued. You can manually enable (or disable) the issuance of the client certificate using the `--[no-]issue-client-certificate` flag. WARNING: Currently VPC-native is not the default mode during cluster creation. In the future, this will become the default mode and can be disabled using `--no-enable-ip-alias` flag. Use `--[no-]enable-ip-alias` flag to suppress this warning. WARNING: Starting in 1.12, default node pools in new clusters will have their legacy Compute Engine instance metadata endpoints disabled by default. To create a cluster with legacy instance metadata endpoints disabled in the default node pool, run `clusters create` with the flag `--metadata disable-legacy-endpoints=true`. WARNING: Your Pod address range (`--cluster-ipv4-cidr`) can accommodate at most 1008 node(s). This will enable the autorepair feature for nodes. Please see https://cloud.google.com/kubernetes-engine/docs/node-auto-repair for more information on node autorepairs. Creating cluster k8s-uzimihsr in us-west1-a... Cluster is being health-checked (master is healthy)...done. Created [https://container.googleapis.com/v1/projects/uzimihsr-01/zones/us-west1-a/clusters/k8s-uzimihsr]. To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-west1-a/k8s-uzimihsr?project=uzimihsr-01 kubeconfig entry generated for k8s-uzimihsr. NAME LOCATION MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS k8s-uzimihsr us-west1-a 1.13.11-gke.14 104.196.227.231 n1-standard-1 1.13.11-gke.14 3 RUNNING クラスタができたので, CLIからこのクラスタと接続するための認証情報を取得する.\n# クラスタ(k8s-uzimihsr)の認証情報を取得する $ gcloud container clusters get-credentials k8s-uzimihsr Fetching cluster endpoint and auth data. kubeconfig entry generated for k8s-uzimihsr. これでこのクラスタを使う準備が完了.\nアプリのデプロイ 作成したクラスタに実際にアプリをデプロイして公開してみる.\n今回は簡単なwebサーバアプリのimageを持つPodをDeploymentで作成し, Serviceを設定してそれを公開する.\n# hello-serverという名前のDeploymentを作成する $ kubectl run hello-server --image gcr.io/google-samples/hello-app:1.0 --port 8080 kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead. deployment.apps/hello-server created # hello-serverのServiceを作成する $ kubectl expose deployment hello-server --type \u0026#34;LoadBalancer\u0026#34; service/hello-server exposed # 公開されているIP(EXTERNAL-IP)を確認 $ kubectl get service hello-server NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE hello-server LoadBalancer 10.27.253.151 34.82.48.96 8080:30758/TCP 71s 実際に公開されているIP( http://34.82.48.96:8080 )を叩くとちゃんとアプリが公開されていることがわかる. うれしい.\nちなみに今回のアプリに使用しているimageはGoで書かれていて, 以下で公開されている.\nmain.go\nDockerfile\nKubernetes Engineクラスタの削除 このままアプリを公開し続けるとGCPの課金が発生するらしい.\n無料トライアル中なので多分関係ないが, 勉強のためにクラスタの削除までやってみる.\n# Service(hello-server)を削除 $ kubectl delete service hello-server service \u0026#34;hello-server\u0026#34; deleted # クラスタの削除 # ちょっと時間がかかる $ gcloud container clusters delete k8s-uzimihsr The following clusters will be deleted. - [k8s-uzimihsr] in [us-west1-a] Do you want to continue (Y/n)? y Deleting cluster k8s-uzimihsr...done. Deleted [https://container.googleapis.com/v1/projects/uzimihsr-01/zones/us-west1-a/clusters/k8s-uzimihsr]. これでクラスタが削除できた.\nよくわかってないけどServiceだけ消してるのはそこに課金が発生するからみたい.\n(アプリを外部公開している時間に対して課金が発生する?)\nおわり CLIから簡単に外部公開用のクラスタが構築できてすげー便利だと思った.\nアプリを開発できる人間が星の数ほどいる現在,\nアプリの開発や公開に便利な環境を提供するサービスはドンドン売れるってはっきりわかんだね. Googleすごい.\n\u0026ldquo;ゴールドラッシュのときに一番儲かったのは金を掘るための道具を売ってた人たち\u0026quot;的な話を思い出した.\n次はKubernetes完全ガイドを読んだ内容をまとめていきたい.\n","date":"2019-11-20T22:39:39+09:00","permalink":"/post/2019-11-20-google-kubernetes-engine/","title":"Google Kubernetes Engineはじめてみた"},{"content":"基本が大事 Hello Minikubeしたので, チュートリアルの続きを読んでみた.\n読んだもの Kubernetesの基本を学ぶ\n元記事 : Learn Kubernetes Basics\n日本語訳がしっかり用意されているので, 読んだ内容を自分の言葉でまとめる.\n動作環境 途中のチュートリアルは以下の環境を使用して行った.\nmasOS Mojave 10.14 Minikube v1.5.0 VirtualBox 6.0.12 もくじ Kubernetesの基本を学ぶ クラスタの作成 アプリケーションのデプロイ アプリケーションの探索 アプリケーションの公開 アプリケーションのスケーリング アプリケーションのアップデート Kubernetesの基本を学ぶ Kubernetesはどんなことができるの?\nコンテナアプリを簡単に実行できるようにしてくれる. そのために必要なリソース, ツールを提供してくれる. クラスタの作成 Kubernetesクラスタとは\nクラスタを管理するMasterとコンテナアプリを動かすNodeで構成される. Masterはクラスタ内の全ての操作を行う. コンテナがNodeで実行されるためのスケジューリングを行う. NodeはVMまたは物理マシンで構成され, クラスタのワーカーとして動作する. Nodeを管理し, Masterと通信するためのKubeletを持つ. 通信はKubernetes APIを介して行う. コンテナを動かすためのDockerを持つ. チュートリアル # Minikubeのバージョン確認 $ minikube version minikube version: v1.5.2 commit: 792dbf92a1de583fcee76f8791cff12e0c9440ad # Kubernetesクラスタの作成(VirtualBoxを使用) $ minikube start --vm-driver=virtualbox 😄 minikube v1.5.2 on Darwin 10.14 🔥 Creating virtualbox VM (CPUs=2, Memory=2000MB, Disk=20000MB) ... 🐳 Preparing Kubernetes v1.16.2 on Docker \u0026#39;18.09.9\u0026#39; ... 🚜 Pulling images ... 🚀 Launching Kubernetes ... ⌛ Waiting for: apiserver 🏄 Done! kubectl is now configured to use \u0026#34;minikube\u0026#34; # kubectlのバージョン確認 $ kubectl version Client Version: version.Info{Major:\u0026#34;1\u0026#34;, Minor:\u0026#34;16\u0026#34;, GitVersion:\u0026#34;v1.16.2\u0026#34;, GitCommit:\u0026#34;c97fe5036ef3df2967d086711e6c0c405941e14b\u0026#34;, GitTreeState:\u0026#34;clean\u0026#34;, BuildDate:\u0026#34;2019-10-15T23:41:55Z\u0026#34;, GoVersion:\u0026#34;go1.12.10\u0026#34;, Compiler:\u0026#34;gc\u0026#34;, Platform:\u0026#34;darwin/amd64\u0026#34;} Server Version: version.Info{Major:\u0026#34;1\u0026#34;, Minor:\u0026#34;16\u0026#34;, GitVersion:\u0026#34;v1.16.2\u0026#34;, GitCommit:\u0026#34;c97fe5036ef3df2967d086711e6c0c405941e14b\u0026#34;, GitTreeState:\u0026#34;clean\u0026#34;, BuildDate:\u0026#34;2019-10-15T19:09:08Z\u0026#34;, GoVersion:\u0026#34;go1.12.10\u0026#34;, Compiler:\u0026#34;gc\u0026#34;, Platform:\u0026#34;linux/amd64\u0026#34;} # クラスタ情報の確認 $ kubectl cluster-info Kubernetes master is running at https://192.168.99.108:8443 KubeDNS is running at https://192.168.99.108:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use \u0026#39;kubectl cluster-info dump\u0026#39;. # Nodeの一覧を確認 $ kubectl get nodes NAME STATUS ROLES AGE VERSION minikube Ready master 107s v1.16.2 アプリケーションのデプロイ Deploymentとは\nアプリケーションインスタンスの作成, 更新方法をKubernetesに指示するもの アプリケーションインスタンスはNodeに作成される MasterにあるDeployment Controllerが各インスタンスを監視する 問題があった場合はセルフヒーリングを行う(別Nodeのインスタンスと置き換える) チュートリアル # Deploymentの作成 # kubernetes-bootcampという名前のDeploymentを作成 # --imageで使用するDocker imageを指定 $ kubectl create deployment kubernetes-bootcamp --image=gcr.io/google-samples/kubernetes-bootcamp:v1 deployment.apps/kubernetes-bootcamp created # Deploymentの一覧を確認 $ kubectl get deployments NAME READY UP-TO-DATE AVAILABLE AGE kubernetes-bootcamp 1/1 1 1 59s # 別のターミナルを開き, ローカルマシンからKubernetes APIに接続するためのproxyを起動 $ kubectl proxy Starting to serve on 127.0.0.1:8001 # 使い終わったらCtrl + Cで終了できる # Kubernetes APIにアクセスしてバージョンを確認 $ curl http://127.0.0.1:8001/version { \u0026#34;major\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;minor\u0026#34;: \u0026#34;16\u0026#34;, \u0026#34;gitVersion\u0026#34;: \u0026#34;v1.16.2\u0026#34;, \u0026#34;gitCommit\u0026#34;: \u0026#34;c97fe5036ef3df2967d086711e6c0c405941e14b\u0026#34;, \u0026#34;gitTreeState\u0026#34;: \u0026#34;clean\u0026#34;, \u0026#34;buildDate\u0026#34;: \u0026#34;2019-10-15T19:09:08Z\u0026#34;, \u0026#34;goVersion\u0026#34;: \u0026#34;go1.12.10\u0026#34;, \u0026#34;compiler\u0026#34;: \u0026#34;gc\u0026#34;, \u0026#34;platform\u0026#34;: \u0026#34;linux/amd64\u0026#34; } # 起動しているPodの名前を取得して環境変数POD_NAMEに代入 $ export POD_NAME=$(kubectl get pods -o go-template --template \u0026#39;{{range .items}}{{.metadata.name}}{{\u0026#34;\\n\u0026#34;}}{{end}}\u0026#39;) $ echo Name of the Pod: $POD_NAME Name of the Pod: kubernetes-bootcamp-69fbc6f4cf-6kppt アプリケーションの探索 Podとは\n1つ以上のコンテナとコンテナの共有リソースを持つ, Kubernetesの最小単位 共有ストレージ, IPアドレス, コンテナの動作に関わる情報を持つ Pod内のコンテナはIPアドレスとポートを共有する Deploymentによって作成/管理される 割り当てられた(スケジュールされた)Node上で動作する Nodeとは\nKubernetesのワーカー 物理マシン, 仮想マシン問わずワーカーとして機能できる 複数のPodを持つことができる Masterによって管理される 同一クラスタのNode間でのPodのスケジューリングが自動で管理される KubeletがMasterとの通信を担当し, Podを管理する Dockerを使用してレジストリからイメージを取得, コンテナの実行を行う チュートリアル # Podの一覧を確認 $ kubectl get pods NAME READY STATUS RESTARTS AGE kubernetes-bootcamp-69fbc6f4cf-6kppt 1/1 Running 0 58m # Podの詳細を確認 $ kubectl describe pods Name: kubernetes-bootcamp-69fbc6f4cf-6kppt Namespace: default Priority: 0 Node: minikube/192.168.99.109 Start Time: Wed, 06 Nov 2019 23:24:56 +0900 Labels: app=kubernetes-bootcamp pod-template-hash=69fbc6f4cf Annotations: \u0026lt;none\u0026gt; Status: Running IP: 172.17.0.6 IPs: IP: 172.17.0.6 Controlled By: ReplicaSet/kubernetes-bootcamp-69fbc6f4cf Containers: kubernetes-bootcamp: Container ID: docker://64bbc713c49fde8d7fa154fc35b53bcf2515cbf12426248da36b11690a11d2f4 Image: gcr.io/google-samples/kubernetes-bootcamp:v1 Image ID: docker-pullable://gcr.io/google-samples/kubernetes-bootcamp@sha256:0d6b8ee63bb57c5f5b6156f446b3bc3b3c143d233037f3a2f00e279c8fcc64af Port: \u0026lt;none\u0026gt; Host Port: \u0026lt;none\u0026gt; State: Running Started: Wed, 06 Nov 2019 23:25:15 +0900 Ready: True Restart Count: 0 Environment: \u0026lt;none\u0026gt; Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-jj2nf (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-jj2nf: Type: Secret (a volume populated by a Secret) SecretName: default-token-jj2nf Optional: false QoS Class: BestEffort Node-Selectors: \u0026lt;none\u0026gt; Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled \u0026lt;unknown\u0026gt; default-scheduler Successfully assigned default/kubernetes-bootcamp-69fbc6f4cf-6kppt to minikube Normal Pulling 59m kubelet, minikube Pulling image \u0026#34;gcr.io/google-samples/kubernetes-bootcamp:v1\u0026#34; Normal Pulled 58m kubelet, minikube Successfully pulled image \u0026#34;gcr.io/google-samples/kubernetes-bootcamp:v1\u0026#34; Normal Created 58m kubelet, minikube Created container kubernetes-bootcamp Normal Started 58m kubelet, minikube Started container kubernetes-bootcamp # proxyは起動済み # $ kubectl proxy # Pod名も取得済み # $ export POD_NAME=$(kubectl get pods -o go-template --template \u0026#39;{{range .items}}{{.metadata.name}}{{\u0026#34;\\n\u0026#34;}}{{end}}\u0026#39;) $ echo Name of the Pod: $POD_NAME Name of the Pod: kubernetes-bootcamp-69fbc6f4cf-6kppt # Kubernetes APIを利用してPodにアクセスする # が, この通りにやると失敗する $ curl http://localhost:8001/api/v1/namespaces/default/pods/$POD_NAME/proxy/ Error trying to reach service: \u0026#39;dial tcp 172.17.0.6:80: connect: connection refused\u0026#39; # 上記コマンドでは失敗するので, 次のコマンドを実行する # 詳細は下記`connection refusedになる問題について`を参照 $ curl http://localhost:8001/api/v1/namespaces/default/pods/$POD_NAME:8080/proxy/ Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-69fbc6f4cf-6kppt | v=1 チュートリアルでconnection refusedになる問題について 使用しているimage(gcr.io/google-samples/kubernetes-bootcamp:v1)が公開しているポートが80番でなく8080番であるにもかかわらず,\nKubernetes APIのGet Connect ProxyでPodにアクセスする際にデフォルトのポート(80番)を叩こうとしていることが原因.\n接続しようとしているPodには80番ポートが割り当てられているコンテナが無いため, 接続が拒否される.\n以下は確認手順.\n# 実際にimageをローカルに持ってくる $ docker pull gcr.io/google-samples/kubernetes-bootcamp:v1 v1: Pulling from google-samples/kubernetes-bootcamp 5c90d4a2d1a8: Pull complete ab30c63719b1: Pull complete 29d0bc1e8c52: Pull complete d4fe0dc68927: Pull complete dfa9e924f957: Pull complete Digest: sha256:0d6b8ee63bb57c5f5b6156f446b3bc3b3c143d233037f3a2f00e279c8fcc64af Status: Downloaded newer image for gcr.io/google-samples/kubernetes-bootcamp:v1 gcr.io/google-samples/kubernetes-bootcamp:v1 # Dockerfileの内容を確認 $ docker image history gcr.io/google-samples/kubernetes-bootcamp:v1 IMAGE CREATED CREATED BY SIZE COMMENT 8fafd8af70e9 3 years ago /bin/sh -c #(nop) CMD [\u0026#34;/bin/sh\u0026#34; \u0026#34;-c\u0026#34; \u0026#34;node… 0B \u0026lt;missing\u0026gt; 3 years ago /bin/sh -c #(nop) COPY file:de8ef36ebbfd5305… 742B \u0026lt;missing\u0026gt; 3 years ago /bin/sh -c #(nop) EXPOSE 8080/tcp 0B \u0026lt;missing\u0026gt; 3 years ago /bin/sh -c #(nop) CMD [\u0026#34;node\u0026#34;] 0B \u0026lt;missing\u0026gt; 3 years ago /bin/sh -c buildDeps=\u0026#34;xz-utils\u0026#34; \u0026amp;\u0026amp; set -… 41.5MB \u0026lt;missing\u0026gt; 3 years ago /bin/sh -c #(nop) ENV NODE_VERSION=6.3.1 0B \u0026lt;missing\u0026gt; 3 years ago /bin/sh -c #(nop) ENV NPM_CONFIG_LOGLEVEL=in… 0B \u0026lt;missing\u0026gt; 3 years ago /bin/sh -c set -ex \u0026amp;\u0026amp; for key in 9554F… 80.8kB \u0026lt;missing\u0026gt; 3 years ago /bin/sh -c apt-get update \u0026amp;\u0026amp; apt-get install… 44.7MB \u0026lt;missing\u0026gt; 3 years ago /bin/sh -c #(nop) CMD [\u0026#34;/bin/bash\u0026#34;] 0B \u0026lt;missing\u0026gt; 3 years ago /bin/sh -c #(nop) ADD file:76679eeb94129df23… 125MB # 上記の通り, このimageは`EXPOSE 8080`で8080番ポートを公開している. # そのため, チュートリアル通りのコマンドではデフォルトの80番ポート(公開されていない)を見ようとするので失敗する. $ curl http://localhost:8001/api/v1/namespaces/default/pods/$POD_NAME/proxy/ Error trying to reach service: \u0026#39;dial tcp 172.17.0.6:80: connect: connection refused\u0026#39; # Pod名の後にポート番号を指定してあげると問題なくアクセスできる. $ curl http://localhost:8001/api/v1/namespaces/default/pods/$POD_NAME:8080/proxy/ Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-69fbc6f4cf-6kppt | v=1 アプリケーションの公開 Serviceとは\nPodを外部に公開するためのもの IPを指定せずにLabelを用いて各Podを指定してアクセスできるようにする役割を持つ 複数Pod間の負荷分散を行う 複数Node間に存在するPod間でも負荷分散が可能 チュートリアル # Podの一覧を確認 $ kubectl get pods NAME READY STATUS RESTARTS AGE kubernetes-bootcamp-69fbc6f4cf-6kppt 1/1 Running 0 4d22h # Serviceの一覧を確認 # kubernetesはMinikube起動時にデフォルトで生成されるService $ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 \u0026lt;none\u0026gt; 443/TCP 4d22h # 既に存在するDeployment(kubernetes-bootcamp)を公開するNodePort Serviceを作成 # $ kubectl expose deployment/kubernetes-bootcamp --type=\u0026#34;NodePort\u0026#34; --port 8080 service/kubernetes-bootcamp exposed # 再度Serviceの一覧を確認 # 新たにService(kubernetes-bootcamp)が作成されていることを確認 $ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 \u0026lt;none\u0026gt; 443/TCP 4d22h kubernetes-bootcamp NodePort 10.106.55.200 \u0026lt;none\u0026gt; 8080:31038/TCP 17s # Service(kubernetes-bootcamp)の詳細を確認 # Nodeの31038番ががPodの8080番に割り当てられている $ kubectl describe services/kubernetes-bootcamp Name: kubernetes-bootcamp Namespace: default Labels: app=kubernetes-bootcamp Annotations: \u0026lt;none\u0026gt; Selector: app=kubernetes-bootcamp Type: NodePort IP: 10.106.55.200 Port: \u0026lt;unset\u0026gt; 8080/TCP TargetPort: 8080/TCP NodePort: \u0026lt;unset\u0026gt; 31038/TCP Endpoints: 172.17.0.6:8080 Session Affinity: None External Traffic Policy: Cluster Events: \u0026lt;none\u0026gt; # Serviceによって公開されているNodeのポート番号を環境変数NODE_PORTに代入 $ export NODE_PORT=$(kubectl get services/kubernetes-bootcamp -o go-template=\u0026#39;{{(index .spec.ports 0).nodePort}}\u0026#39;) $ echo NODE_PORT=$NODE_PORT NODE_PORT=31038 # 公開されているポート番号へアクセス $ curl $(minikube ip):$NODE_PORT Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-69fbc6f4cf-6kppt | v=1 # 起動しているDeploymentの詳細を確認 # デフォルトでLabel(app=kubernetes-bootcamp)がつけられている $ kubectl describe deployment Name: kubernetes-bootcamp Namespace: default CreationTimestamp: Wed, 06 Nov 2019 23:24:56 +0900 Labels: app=kubernetes-bootcamp Annotations: deployment.kubernetes.io/revision: 1 Selector: app=kubernetes-bootcamp Replicas: 1 desired | 1 updated | 1 total | 1 available | 0 unavailable StrategyType: RollingUpdate MinReadySeconds: 0 RollingUpdateStrategy: 25% max unavailable, 25% max surge Pod Template: Labels: app=kubernetes-bootcamp Containers: kubernetes-bootcamp: Image: gcr.io/google-samples/kubernetes-bootcamp:v1 Port: \u0026lt;none\u0026gt; Host Port: \u0026lt;none\u0026gt; Environment: \u0026lt;none\u0026gt; Mounts: \u0026lt;none\u0026gt; Volumes: \u0026lt;none\u0026gt; Conditions: Type Status Reason ---- ------ ------ Available True MinimumReplicasAvailable Progressing True NewReplicaSetAvailable OldReplicaSets: \u0026lt;none\u0026gt; NewReplicaSet: kubernetes-bootcamp-69fbc6f4cf (1/1 replicas created) Events: \u0026lt;none\u0026gt; # Label(run=kubernetes-bootcamp)を指定してPodを表示 $ kubectl get pods -l run=kubernetes-bootcamp NAME READY STATUS RESTARTS AGE kubernetes-bootcamp-69fbc6f4cf-6kppt 1/1 Running 0 4d23h # 同様にServiceもLabelで絞り込んで表示 $ kubectl get services -l app=kubernetes-bootcamp NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes-bootcamp NodePort 10.106.55.200 \u0026lt;none\u0026gt; 8080:31038/TCP 16m # 環境変数POD_NAMEにPod名を代入 $ export POD_NAME=$(kubectl get pods -o go-template --template \u0026#39;{{range .items}}{{.metadata.name}}{{\u0026#34;\\n\u0026#34;}}{{end}}\u0026#39;) $ echo Name of the Pod: $POD_NAME Name of the Pod: kubernetes-bootcamp-69fbc6f4cf-6kppt # Pod(kubernetes-bootcamp-69fbc6f4cf-6kppt)にLabel(app=v1)を追加 # 既にkey(app)に対してvalue(kubernetes-bootcamp)がついていて, --overwriteを指定していないので失敗する $ kubectl label pod $POD_NAME app=v1 error: \u0026#39;app\u0026#39; already has a value (kubernetes-bootcamp), and --overwrite is false # 手順とは異なるが別のkey:valueを指定してみる $ kubectl label pod $POD_NAME app_hoge=v1 pod/kubernetes-bootcamp-69fbc6f4cf-6kppt labeled # Pod(kubernetes-bootcamp-69fbc6f4cf-6kppt)の詳細を確認 # Label(app_hoge=v1)が追加されている $ kubectl describe pods $POD_NAME Name: kubernetes-bootcamp-69fbc6f4cf-6kppt Namespace: default Priority: 0 Node: minikube/192.168.99.109 Start Time: Wed, 06 Nov 2019 23:24:56 +0900 Labels: app=kubernetes-bootcamp app_hoge=v1 pod-template-hash=69fbc6f4cf Annotations: \u0026lt;none\u0026gt; Status: Running IP: 172.17.0.6 IPs: IP: 172.17.0.6 Controlled By: ReplicaSet/kubernetes-bootcamp-69fbc6f4cf Containers: kubernetes-bootcamp: Container ID: docker://64bbc713c49fde8d7fa154fc35b53bcf2515cbf12426248da36b11690a11d2f4 Image: gcr.io/google-samples/kubernetes-bootcamp:v1 Image ID: docker-pullable://gcr.io/google-samples/kubernetes-bootcamp@sha256:0d6b8ee63bb57c5f5b6156f446b3bc3b3c143d233037f3a2f00e279c8fcc64af Port: \u0026lt;none\u0026gt; Host Port: \u0026lt;none\u0026gt; State: Running Started: Wed, 06 Nov 2019 23:25:15 +0900 Ready: True Restart Count: 0 Environment: \u0026lt;none\u0026gt; Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-jj2nf (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-jj2nf: Type: Secret (a volume populated by a Secret) SecretName: default-token-jj2nf Optional: false QoS Class: BestEffort Node-Selectors: \u0026lt;none\u0026gt; Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: \u0026lt;none\u0026gt; # Label(app_hoge=v1)を持つPodを表示 $ kubectl get pods -l app_hoge=v1 NAME READY STATUS RESTARTS AGE kubernetes-bootcamp-69fbc6f4cf-6kppt 1/1 Running 0 4d23h # Label(app=kubernetes-bootcamp)を指定してServiceを削除 $ kubectl delete service -l app=kubernetes-bootcamp service \u0026#34;kubernetes-bootcamp\u0026#34; deleted # 再度Serviceの一覧を確認 # kubernetes-bootcampが削除されている $ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 \u0026lt;none\u0026gt; 443/TCP 4d23h # 再度ポートにアクセスする # Serviceが削除され, 31038番が公開されていないので失敗する $ curl $(minikube ip):$NODE_PORT curl: (7) Failed to connect to 192.168.99.109 port 31038: Connection refused # Podの中からアクセスする # Serviceがなくてもアプリケーションは生きていることが確認できる $ kubectl exec -ti $POD_NAME curl localhost:8080 Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-69fbc6f4cf-6kppt | v=1 アプリケーションのスケーリング Deploymentによるスケーリング\nスケーリングはDeploymentのReplicasを変更することで行う Replicasが増えた場合は利用可能なNodeに新しいPodが作成される 新たに生成されたPodへのトラフィック設定はServiceで管理する チュートリアル # Deploymentの一覧を表示 # READYは(起動しているPod数)/(設定されているPod数) # UP-TO-DATEは指定の状態になっているPod数 # AVAILABLEはユーザーが利用可能なPod数 $ kubectl get deployments NAME READY UP-TO-DATE AVAILABLE AGE kubernetes-bootcamp 1/1 1 1 5d # Deployment(kubernetes-bootcamp)のReplicasを4に増やす $ kubectl scale deployments/kubernetes-bootcamp --replicas=4 deployment.apps/kubernetes-bootcamp scaled # 再度Deploymentの一覧を表示 # Pod数が指定した値(4)に変わっている $ kubectl get deployments NAME READY UP-TO-DATE AVAILABLE AGE kubernetes-bootcamp 4/4 4 4 5d # Podの一覧を表示 # 確かにPodが4つそれぞれ別IPで稼働している $ kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES kubernetes-bootcamp-69fbc6f4cf-6kppt 1/1 Running 0 5d 172.17.0.6 minikube \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kubernetes-bootcamp-69fbc6f4cf-9htwb 1/1 Running 0 11m 172.17.0.9 minikube \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kubernetes-bootcamp-69fbc6f4cf-cm8pb 1/1 Running 0 11m 172.17.0.7 minikube \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kubernetes-bootcamp-69fbc6f4cf-j5kq2 1/1 Running 0 11m 172.17.0.8 minikube \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; # Deployment(kubernetes-bootcamp)の詳細表示 # Replicasが4に変わっている # 変更履歴はEventに記録される $ kubectl describe deployments/kubernetes-bootcamp Name: kubernetes-bootcamp Namespace: default CreationTimestamp: Wed, 06 Nov 2019 23:24:56 +0900 Labels: app=kubernetes-bootcamp Annotations: deployment.kubernetes.io/revision: 1 Selector: app=kubernetes-bootcamp Replicas: 4 desired | 4 updated | 4 total | 4 available | 0 unavailable StrategyType: RollingUpdate MinReadySeconds: 0 RollingUpdateStrategy: 25% max unavailable, 25% max surge Pod Template: Labels: app=kubernetes-bootcamp Containers: kubernetes-bootcamp: Image: gcr.io/google-samples/kubernetes-bootcamp:v1 Port: \u0026lt;none\u0026gt; Host Port: \u0026lt;none\u0026gt; Environment: \u0026lt;none\u0026gt; Mounts: \u0026lt;none\u0026gt; Volumes: \u0026lt;none\u0026gt; Conditions: Type Status Reason ---- ------ ------ Progressing True NewReplicaSetAvailable Available True MinimumReplicasAvailable OldReplicaSets: \u0026lt;none\u0026gt; NewReplicaSet: kubernetes-bootcamp-69fbc6f4cf (4/4 replicas created) Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal ScalingReplicaSet 6m48s deployment-controller Scaled up replica set kubernetes-bootcamp-69fbc6f4cf to 4 # 手順にはないが前のチュートリアルでServiceを削除してしまっているので再度作成 $ kubectl expose deployment/kubernetes-bootcamp --type=\u0026#34;NodePort\u0026#34; --port 8080 service/kubernetes-bootcamp exposed # Service(kubernetes-bootcamp)の詳細を確認 # Deployment(kubernetes-bootcamp)が管理する4つのPodが接続されている $ kubectl describe services/kubernetes-bootcamp Name: kubernetes-bootcamp Namespace: default Labels: app=kubernetes-bootcamp Annotations: \u0026lt;none\u0026gt; Selector: app=kubernetes-bootcamp Type: NodePort IP: 10.111.26.43 Port: \u0026lt;unset\u0026gt; 8080/TCP TargetPort: 8080/TCP NodePort: \u0026lt;unset\u0026gt; 31189/TCP Endpoints: 172.17.0.6:8080,172.17.0.7:8080,172.17.0.8:8080 + 1 more... Session Affinity: None External Traffic Policy: Cluster Events: \u0026lt;none\u0026gt; # 環境変数NODE_PORTに公開されているNodeのポート番号を代入 $ export NODE_PORT=$(kubectl get services/kubernetes-bootcamp -o go-template=\u0026#39;{{(index .spec.ports 0).nodePort}}\u0026#39;) $ echo NODE_PORT=$NODE_PORT NODE_PORT=31189 # アクセスするたびに違うPodからレスポンスが返ってくる # 負荷分散ができている $ curl $(minikube ip):$NODE_PORT Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-69fbc6f4cf-6kppt | v=1 $ curl $(minikube ip):$NODE_PORT Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-69fbc6f4cf-cm8pb | v=1 $ curl $(minikube ip):$NODE_PORT Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-69fbc6f4cf-j5kq2 | v=1 $ curl $(minikube ip):$NODE_PORT Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-69fbc6f4cf-j5kq2 | v=1 $ curl $(minikube ip):$NODE_PORT Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-69fbc6f4cf-9htwb | v=1 # Deployment(kubernetes-bootcamp)のReplicasを2に減らす $ kubectl scale deployments/kubernetes-bootcamp --replicas=2 deployment.apps/kubernetes-bootcamp scaled # Deploymentの一覧を確認 # Pod数が2に減っている $ kubectl get deployments NAME READY UP-TO-DATE AVAILABLE AGE kubernetes-bootcamp 2/2 2 2 5d # Podの一覧を表示 # 4つあったPodのうち2つが消えている $ kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES kubernetes-bootcamp-69fbc6f4cf-6kppt 1/1 Running 0 5d 172.17.0.6 minikube \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; kubernetes-bootcamp-69fbc6f4cf-9htwb 1/1 Running 0 19m 172.17.0.9 minikube \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; アプリケーションのアップデート ローリングアップデートとは\nPodを段階的にアップデートすること ダウンタイムが発生しない チュートリアル # 手順にはないが後半の手順とPod数を合わせるためスケールアウトする $ kubectl scale deployments/kubernetes-bootcamp --replicas=4 deployment.apps/kubernetes-bootcamp scaled # Deploymentの一覧を確認 $ kubectl get deployments NAME READY UP-TO-DATE AVAILABLE AGE kubernetes-bootcamp 4/4 4 4 5d23h # Podの一覧を確認 $ kubectl get pods NAME READY STATUS RESTARTS AGE kubernetes-bootcamp-69fbc6f4cf-6hx6m 1/1 Running 0 57s kubernetes-bootcamp-69fbc6f4cf-6kppt 1/1 Running 0 5d23h kubernetes-bootcamp-69fbc6f4cf-6rvk4 1/1 Running 0 57s kubernetes-bootcamp-69fbc6f4cf-9htwb 1/1 Running 0 23h # Podの詳細を確認 $ kubectl describe pods Name: kubernetes-bootcamp-69fbc6f4cf-6hx6m Namespace: default Priority: 0 Node: minikube/192.168.99.109 Start Time: Tue, 12 Nov 2019 23:05:57 +0900 Labels: app=kubernetes-bootcamp pod-template-hash=69fbc6f4cf Annotations: \u0026lt;none\u0026gt; Status: Running IP: 172.17.0.7 IPs: IP: 172.17.0.7 Controlled By: ReplicaSet/kubernetes-bootcamp-69fbc6f4cf Containers: kubernetes-bootcamp: Container ID: docker://4a1577f4cc007bbdc62d54afb048b3c67c622e9d4fa4fcbc7bb4e7a7014a8e70 Image: gcr.io/google-samples/kubernetes-bootcamp:v1 Image ID: docker-pullable://gcr.io/google-samples/kubernetes-bootcamp@sha256:0d6b8ee63bb57c5f5b6156f446b3bc3b3c143d233037f3a2f00e279c8fcc64af Port: \u0026lt;none\u0026gt; Host Port: \u0026lt;none\u0026gt; State: Running Started: Tue, 12 Nov 2019 23:05:58 +0900 Ready: True Restart Count: 0 Environment: \u0026lt;none\u0026gt; Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-jj2nf (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-jj2nf: Type: Secret (a volume populated by a Secret) SecretName: default-token-jj2nf Optional: false QoS Class: BestEffort Node-Selectors: \u0026lt;none\u0026gt; Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled \u0026lt;unknown\u0026gt; default-scheduler Successfully assigned default/kubernetes-bootcamp-69fbc6f4cf-6hx6m to minikube Normal Pulled 91s kubelet, minikube Container image \u0026#34;gcr.io/google-samples/kubernetes-bootcamp:v1\u0026#34; already present on machine Normal Created 91s kubelet, minikube Created container kubernetes-bootcamp Normal Started 91s kubelet, minikube Started container kubernetes-bootcamp Name: kubernetes-bootcamp-69fbc6f4cf-6kppt Namespace: default Priority: 0 Node: minikube/192.168.99.109 Start Time: Wed, 06 Nov 2019 23:24:56 +0900 Labels: app=kubernetes-bootcamp app_hoge=v1 pod-template-hash=69fbc6f4cf Annotations: \u0026lt;none\u0026gt; Status: Running IP: 172.17.0.6 IPs: IP: 172.17.0.6 Controlled By: ReplicaSet/kubernetes-bootcamp-69fbc6f4cf Containers: kubernetes-bootcamp: Container ID: docker://64bbc713c49fde8d7fa154fc35b53bcf2515cbf12426248da36b11690a11d2f4 Image: gcr.io/google-samples/kubernetes-bootcamp:v1 Image ID: docker-pullable://gcr.io/google-samples/kubernetes-bootcamp@sha256:0d6b8ee63bb57c5f5b6156f446b3bc3b3c143d233037f3a2f00e279c8fcc64af Port: \u0026lt;none\u0026gt; Host Port: \u0026lt;none\u0026gt; State: Running Started: Wed, 06 Nov 2019 23:25:15 +0900 Ready: True Restart Count: 0 Environment: \u0026lt;none\u0026gt; Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-jj2nf (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-jj2nf: Type: Secret (a volume populated by a Secret) SecretName: default-token-jj2nf Optional: false QoS Class: BestEffort Node-Selectors: \u0026lt;none\u0026gt; Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: \u0026lt;none\u0026gt; Name: kubernetes-bootcamp-69fbc6f4cf-6rvk4 Namespace: default Priority: 0 Node: minikube/192.168.99.109 Start Time: Tue, 12 Nov 2019 23:05:57 +0900 Labels: app=kubernetes-bootcamp pod-template-hash=69fbc6f4cf Annotations: \u0026lt;none\u0026gt; Status: Running IP: 172.17.0.8 IPs: IP: 172.17.0.8 Controlled By: ReplicaSet/kubernetes-bootcamp-69fbc6f4cf Containers: kubernetes-bootcamp: Container ID: docker://124b92bb9f17895f808deee23916dccc6efb95af2fd7e6d8214af7f3ad0bdf97 Image: gcr.io/google-samples/kubernetes-bootcamp:v1 Image ID: docker-pullable://gcr.io/google-samples/kubernetes-bootcamp@sha256:0d6b8ee63bb57c5f5b6156f446b3bc3b3c143d233037f3a2f00e279c8fcc64af Port: \u0026lt;none\u0026gt; Host Port: \u0026lt;none\u0026gt; State: Running Started: Tue, 12 Nov 2019 23:05:58 +0900 Ready: True Restart Count: 0 Environment: \u0026lt;none\u0026gt; Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-jj2nf (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-jj2nf: Type: Secret (a volume populated by a Secret) SecretName: default-token-jj2nf Optional: false QoS Class: BestEffort Node-Selectors: \u0026lt;none\u0026gt; Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled \u0026lt;unknown\u0026gt; default-scheduler Successfully assigned default/kubernetes-bootcamp-69fbc6f4cf-6rvk4 to minikube Normal Pulled 91s kubelet, minikube Container image \u0026#34;gcr.io/google-samples/kubernetes-bootcamp:v1\u0026#34; already present on machine Normal Created 91s kubelet, minikube Created container kubernetes-bootcamp Normal Started 91s kubelet, minikube Started container kubernetes-bootcamp Name: kubernetes-bootcamp-69fbc6f4cf-9htwb Namespace: default Priority: 0 Node: minikube/192.168.99.109 Start Time: Mon, 11 Nov 2019 23:35:24 +0900 Labels: app=kubernetes-bootcamp pod-template-hash=69fbc6f4cf Annotations: \u0026lt;none\u0026gt; Status: Running IP: 172.17.0.9 IPs: IP: 172.17.0.9 Controlled By: ReplicaSet/kubernetes-bootcamp-69fbc6f4cf Containers: kubernetes-bootcamp: Container ID: docker://ce75a00dbebf8ede509c4bf2776029666518022ee1b164aaced4c7edc90def8d Image: gcr.io/google-samples/kubernetes-bootcamp:v1 Image ID: docker-pullable://gcr.io/google-samples/kubernetes-bootcamp@sha256:0d6b8ee63bb57c5f5b6156f446b3bc3b3c143d233037f3a2f00e279c8fcc64af Port: \u0026lt;none\u0026gt; Host Port: \u0026lt;none\u0026gt; State: Running Started: Mon, 11 Nov 2019 23:35:25 +0900 Ready: True Restart Count: 0 Environment: \u0026lt;none\u0026gt; Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-jj2nf (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-jj2nf: Type: Secret (a volume populated by a Secret) SecretName: default-token-jj2nf Optional: false QoS Class: BestEffort Node-Selectors: \u0026lt;none\u0026gt; Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled \u0026lt;unknown\u0026gt; default-scheduler Successfully assigned default/kubernetes-bootcamp-69fbc6f4cf-9htwb to minikube Normal Pulled 23h kubelet, minikube Container image \u0026#34;gcr.io/google-samples/kubernetes-bootcamp:v1\u0026#34; already present on machine Normal Created 23h kubelet, minikube Created container kubernetes-bootcamp Normal Started 23h kubelet, minikube Started container kubernetes-bootcamp # Deployment(kubernetes-bootcamp)で使用しているimage(kubernetes-bootcamp)のバージョンを変更 $ kubectl set image deployments/kubernetes-bootcamp kubernetes-bootcamp=jocatalin/kubernetes-bootcamp:v2 deployment.apps/kubernetes-bootcamp image updated # 何度かPodの一覧を確認すると順番に古いPodが削除/新しいPodが生成されていることがわかる $ kubectl get pods NAME READY STATUS RESTARTS AGE kubernetes-bootcamp-69fbc6f4cf-6hx6m 1/1 Terminating 0 2m53s kubernetes-bootcamp-69fbc6f4cf-6kppt 1/1 Running 0 5d23h kubernetes-bootcamp-69fbc6f4cf-6rvk4 1/1 Terminating 0 2m53s kubernetes-bootcamp-69fbc6f4cf-9htwb 1/1 Running 0 23h kubernetes-bootcamp-b4d9f565-ftfn6 0/1 ContainerCreating 0 6s kubernetes-bootcamp-b4d9f565-vd6fb 1/1 Running 0 6s kubernetes-bootcamp-b4d9f565-wwxnf 0/1 ContainerCreating 0 1s $ kubectl get pods NAME READY STATUS RESTARTS AGE kubernetes-bootcamp-69fbc6f4cf-6hx6m 1/1 Terminating 0 2m56s kubernetes-bootcamp-69fbc6f4cf-6kppt 1/1 Terminating 0 5d23h kubernetes-bootcamp-69fbc6f4cf-6rvk4 1/1 Terminating 0 2m56s kubernetes-bootcamp-69fbc6f4cf-9htwb 1/1 Terminating 0 23h kubernetes-bootcamp-b4d9f565-ftfn6 1/1 Running 0 9s kubernetes-bootcamp-b4d9f565-qxjhr 1/1 Running 0 2s kubernetes-bootcamp-b4d9f565-vd6fb 1/1 Running 0 9s kubernetes-bootcamp-b4d9f565-wwxnf 1/1 Running 0 4s $ kubectl get pods NAME READY STATUS RESTARTS AGE kubernetes-bootcamp-b4d9f565-ftfn6 1/1 Running 0 48s kubernetes-bootcamp-b4d9f565-qxjhr 1/1 Running 0 41s kubernetes-bootcamp-b4d9f565-vd6fb 1/1 Running 0 48s kubernetes-bootcamp-b4d9f565-wwxnf 1/1 Running 0 43s # Service(kubernetes-bootcamp)の詳細を確認 $ kubectl describe services/kubernetes-bootcamp Name: kubernetes-bootcamp Namespace: default Labels: app=kubernetes-bootcamp Annotations: \u0026lt;none\u0026gt; Selector: app=kubernetes-bootcamp Type: NodePort IP: 10.111.26.43 Port: \u0026lt;unset\u0026gt; 8080/TCP TargetPort: 8080/TCP NodePort: \u0026lt;unset\u0026gt; 31189/TCP Endpoints: 172.17.0.10:8080,172.17.0.11:8080,172.17.0.12:8080 + 1 more... Session Affinity: None External Traffic Policy: Cluster Events: \u0026lt;none\u0026gt; # 環境変数NODE_PORTにService(kubernetes-bootcamp)で公開されているポート番号を代入 $ export NODE_PORT=$(kubectl get services/kubernetes-bootcamp -o go-template=\u0026#39;{{(index .spec.ports 0).nodePort}}\u0026#39;) $ echo NODE_PORT=$NODE_PORT NODE_PORT=31189 # 公開されているポートにアクセス $ curl $(minikube ip):$NODE_PORT Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-b4d9f565-mds8p | v=2 # ローリングアップデートが成功したか確認する $ kubectl rollout status deployments/kubernetes-bootcamp deployment \u0026#34;kubernetes-bootcamp\u0026#34; successfully rolled out # Podの詳細を確認 # Imageがすべてv2に変わっている $ kubectl describe pods Name: kubernetes-bootcamp-b4d9f565-4wkhr Namespace: default Priority: 0 Node: minikube/192.168.99.109 Start Time: Tue, 12 Nov 2019 23:13:59 +0900 Labels: app=kubernetes-bootcamp pod-template-hash=b4d9f565 Annotations: \u0026lt;none\u0026gt; Status: Running IP: 172.17.0.11 IPs: IP: 172.17.0.11 Controlled By: ReplicaSet/kubernetes-bootcamp-b4d9f565 Containers: kubernetes-bootcamp: Container ID: docker://c8bebac0609ee166972beae7437c71733955bfde4107708d28fe3606d68e44d6 Image: jocatalin/kubernetes-bootcamp:v2 Image ID: docker-pullable://jocatalin/kubernetes-bootcamp@sha256:fb1a3ced00cecfc1f83f18ab5cd14199e30adc1b49aa4244f5d65ad3f5feb2a5 Port: \u0026lt;none\u0026gt; Host Port: \u0026lt;none\u0026gt; State: Running Started: Tue, 12 Nov 2019 23:14:00 +0900 Ready: True Restart Count: 0 Environment: \u0026lt;none\u0026gt; Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-jj2nf (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-jj2nf: Type: Secret (a volume populated by a Secret) SecretName: default-token-jj2nf Optional: false QoS Class: BestEffort Node-Selectors: \u0026lt;none\u0026gt; Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled \u0026lt;unknown\u0026gt; default-scheduler Successfully assigned default/kubernetes-bootcamp-b4d9f565-4wkhr to minikube Normal Pulled 7m23s kubelet, minikube Container image \u0026#34;jocatalin/kubernetes-bootcamp:v2\u0026#34; already present on machine Normal Created 7m23s kubelet, minikube Created container kubernetes-bootcamp Normal Started 7m22s kubelet, minikube Started container kubernetes-bootcamp Name: kubernetes-bootcamp-b4d9f565-fnkvz Namespace: default Priority: 0 Node: minikube/192.168.99.109 Start Time: Tue, 12 Nov 2019 23:13:59 +0900 Labels: app=kubernetes-bootcamp pod-template-hash=b4d9f565 Annotations: \u0026lt;none\u0026gt; Status: Running IP: 172.17.0.10 IPs: IP: 172.17.0.10 Controlled By: ReplicaSet/kubernetes-bootcamp-b4d9f565 Containers: kubernetes-bootcamp: Container ID: docker://896cc268b334f8241463d9c66af5c7b1accc909c86ea304e95886709b28dedf0 Image: jocatalin/kubernetes-bootcamp:v2 Image ID: docker-pullable://jocatalin/kubernetes-bootcamp@sha256:fb1a3ced00cecfc1f83f18ab5cd14199e30adc1b49aa4244f5d65ad3f5feb2a5 Port: \u0026lt;none\u0026gt; Host Port: \u0026lt;none\u0026gt; State: Running Started: Tue, 12 Nov 2019 23:14:00 +0900 Ready: True Restart Count: 0 Environment: \u0026lt;none\u0026gt; Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-jj2nf (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-jj2nf: Type: Secret (a volume populated by a Secret) SecretName: default-token-jj2nf Optional: false QoS Class: BestEffort Node-Selectors: \u0026lt;none\u0026gt; Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled \u0026lt;unknown\u0026gt; default-scheduler Successfully assigned default/kubernetes-bootcamp-b4d9f565-fnkvz to minikube Normal Pulled 7m23s kubelet, minikube Container image \u0026#34;jocatalin/kubernetes-bootcamp:v2\u0026#34; already present on machine Normal Created 7m23s kubelet, minikube Created container kubernetes-bootcamp Normal Started 7m22s kubelet, minikube Started container kubernetes-bootcamp Name: kubernetes-bootcamp-b4d9f565-mds8p Namespace: default Priority: 0 Node: minikube/192.168.99.109 Start Time: Tue, 12 Nov 2019 23:14:00 +0900 Labels: app=kubernetes-bootcamp pod-template-hash=b4d9f565 Annotations: \u0026lt;none\u0026gt; Status: Running IP: 172.17.0.12 IPs: IP: 172.17.0.12 Controlled By: ReplicaSet/kubernetes-bootcamp-b4d9f565 Containers: kubernetes-bootcamp: Container ID: docker://67e064117055eb3050d2fcc7aaeca90fbfa206ce17943c4746103d783e18b4cf Image: jocatalin/kubernetes-bootcamp:v2 Image ID: docker-pullable://jocatalin/kubernetes-bootcamp@sha256:fb1a3ced00cecfc1f83f18ab5cd14199e30adc1b49aa4244f5d65ad3f5feb2a5 Port: \u0026lt;none\u0026gt; Host Port: \u0026lt;none\u0026gt; State: Running Started: Tue, 12 Nov 2019 23:14:01 +0900 Ready: True Restart Count: 0 Environment: \u0026lt;none\u0026gt; Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-jj2nf (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-jj2nf: Type: Secret (a volume populated by a Secret) SecretName: default-token-jj2nf Optional: false QoS Class: BestEffort Node-Selectors: \u0026lt;none\u0026gt; Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled \u0026lt;unknown\u0026gt; default-scheduler Successfully assigned default/kubernetes-bootcamp-b4d9f565-mds8p to minikube Normal Pulled 7m21s kubelet, minikube Container image \u0026#34;jocatalin/kubernetes-bootcamp:v2\u0026#34; already present on machine Normal Created 7m21s kubelet, minikube Created container kubernetes-bootcamp Normal Started 7m21s kubelet, minikube Started container kubernetes-bootcamp Name: kubernetes-bootcamp-b4d9f565-z7t47 Namespace: default Priority: 0 Node: minikube/192.168.99.109 Start Time: Tue, 12 Nov 2019 23:14:01 +0900 Labels: app=kubernetes-bootcamp pod-template-hash=b4d9f565 Annotations: \u0026lt;none\u0026gt; Status: Running IP: 172.17.0.13 IPs: IP: 172.17.0.13 Controlled By: ReplicaSet/kubernetes-bootcamp-b4d9f565 Containers: kubernetes-bootcamp: Container ID: docker://e41971ea038199381f5a2ac385e984b1dfc6c7564101b88d104df9d008cb45cb Image: jocatalin/kubernetes-bootcamp:v2 Image ID: docker-pullable://jocatalin/kubernetes-bootcamp@sha256:fb1a3ced00cecfc1f83f18ab5cd14199e30adc1b49aa4244f5d65ad3f5feb2a5 Port: \u0026lt;none\u0026gt; Host Port: \u0026lt;none\u0026gt; State: Running Started: Tue, 12 Nov 2019 23:14:02 +0900 Ready: True Restart Count: 0 Environment: \u0026lt;none\u0026gt; Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-jj2nf (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-jj2nf: Type: Secret (a volume populated by a Secret) SecretName: default-token-jj2nf Optional: false QoS Class: BestEffort Node-Selectors: \u0026lt;none\u0026gt; Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled \u0026lt;unknown\u0026gt; default-scheduler Successfully assigned default/kubernetes-bootcamp-b4d9f565-z7t47 to minikube Normal Pulled 7m20s kubelet, minikube Container image \u0026#34;jocatalin/kubernetes-bootcamp:v2\u0026#34; already present on machine Normal Created 7m20s kubelet, minikube Created container kubernetes-bootcamp Normal Started 7m20s kubelet, minikube Started container kubernetes-bootcamp # 今度はv10を指定してアップデートしてみる $ kubectl set image deployments/kubernetes-bootcamp kubernetes-bootcamp=gcr.io/google-samples/kubernetes-bootcamp:v10 deployment.apps/kubernetes-bootcamp image updated # Deploymentの一覧を確認 # Pod数がおかしいことになっている $ kubectl get deployments NAME READY UP-TO-DATE AVAILABLE AGE kubernetes-bootcamp 3/4 2 3 5d23h # Podの一覧を確認 # Statusがおかしいものがある $ kubectl get pods NAME READY STATUS RESTARTS AGE kubernetes-bootcamp-6b4c55d8fc-2rb26 0/1 ImagePullBackOff 0 81s kubernetes-bootcamp-6b4c55d8fc-z9gn7 0/1 ImagePullBackOff 0 81s kubernetes-bootcamp-b4d9f565-4wkhr 1/1 Running 0 11m kubernetes-bootcamp-b4d9f565-fnkvz 1/1 Running 0 11m kubernetes-bootcamp-b4d9f565-mds8p 1/1 Running 0 11m # Podの詳細を確認 # Eventsを見るとv10が見つからずエラーとなっている $ kubectl describe pods Name: kubernetes-bootcamp-6b4c55d8fc-2rb26 Namespace: default Priority: 0 Node: minikube/192.168.99.109 Start Time: Tue, 12 Nov 2019 23:23:59 +0900 Labels: app=kubernetes-bootcamp pod-template-hash=6b4c55d8fc Annotations: \u0026lt;none\u0026gt; Status: Pending IP: 172.17.0.7 IPs: IP: 172.17.0.7 Controlled By: ReplicaSet/kubernetes-bootcamp-6b4c55d8fc Containers: kubernetes-bootcamp: Container ID: Image: gcr.io/google-samples/kubernetes-bootcamp:v10 Image ID: Port: \u0026lt;none\u0026gt; Host Port: \u0026lt;none\u0026gt; State: Waiting Reason: ImagePullBackOff Ready: False Restart Count: 0 Environment: \u0026lt;none\u0026gt; Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-jj2nf (ro) Conditions: Type Status Initialized True Ready False ContainersReady False PodScheduled True Volumes: default-token-jj2nf: Type: Secret (a volume populated by a Secret) SecretName: default-token-jj2nf Optional: false QoS Class: BestEffort Node-Selectors: \u0026lt;none\u0026gt; Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled \u0026lt;unknown\u0026gt; default-scheduler Successfully assigned default/kubernetes-bootcamp-6b4c55d8fc-2rb26 to minikube Normal Pulling 41s (x4 over 2m18s) kubelet, minikube Pulling image \u0026#34;gcr.io/google-samples/kubernetes-bootcamp:v10\u0026#34; Warning Failed 40s (x4 over 2m16s) kubelet, minikube Failed to pull image \u0026#34;gcr.io/google-samples/kubernetes-bootcamp:v10\u0026#34;: rpc error: code = Unknown desc = Error response from daemon: manifest for gcr.io/google-samples/kubernetes-bootcamp:v10 not found Warning Failed 40s (x4 over 2m16s) kubelet, minikube Error: ErrImagePull Warning Failed 29s (x6 over 2m16s) kubelet, minikube Error: ImagePullBackOff Normal BackOff 17s (x7 over 2m16s) kubelet, minikube Back-off pulling image \u0026#34;gcr.io/google-samples/kubernetes-bootcamp:v10\u0026#34; Name: kubernetes-bootcamp-6b4c55d8fc-z9gn7 Namespace: default Priority: 0 Node: minikube/192.168.99.109 Start Time: Tue, 12 Nov 2019 23:23:59 +0900 Labels: app=kubernetes-bootcamp pod-template-hash=6b4c55d8fc Annotations: \u0026lt;none\u0026gt; Status: Pending IP: 172.17.0.6 IPs: IP: 172.17.0.6 Controlled By: ReplicaSet/kubernetes-bootcamp-6b4c55d8fc Containers: kubernetes-bootcamp: Container ID: Image: gcr.io/google-samples/kubernetes-bootcamp:v10 Image ID: Port: \u0026lt;none\u0026gt; Host Port: \u0026lt;none\u0026gt; State: Waiting Reason: ImagePullBackOff Ready: False Restart Count: 0 Environment: \u0026lt;none\u0026gt; Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-jj2nf (ro) Conditions: Type Status Initialized True Ready False ContainersReady False PodScheduled True Volumes: default-token-jj2nf: Type: Secret (a volume populated by a Secret) SecretName: default-token-jj2nf Optional: false QoS Class: BestEffort Node-Selectors: \u0026lt;none\u0026gt; Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled \u0026lt;unknown\u0026gt; default-scheduler Successfully assigned default/kubernetes-bootcamp-6b4c55d8fc-z9gn7 to minikube Normal BackOff 61s (x6 over 2m17s) kubelet, minikube Back-off pulling image \u0026#34;gcr.io/google-samples/kubernetes-bootcamp:v10\u0026#34; Normal Pulling 46s (x4 over 2m18s) kubelet, minikube Pulling image \u0026#34;gcr.io/google-samples/kubernetes-bootcamp:v10\u0026#34; Warning Failed 45s (x4 over 2m17s) kubelet, minikube Failed to pull image \u0026#34;gcr.io/google-samples/kubernetes-bootcamp:v10\u0026#34;: rpc error: code = Unknown desc = Error response from daemon: manifest for gcr.io/google-samples/kubernetes-bootcamp:v10 not found Warning Failed 45s (x4 over 2m17s) kubelet, minikube Error: ErrImagePull Warning Failed 31s (x7 over 2m17s) kubelet, minikube Error: ImagePullBackOff Name: kubernetes-bootcamp-b4d9f565-4wkhr Namespace: default Priority: 0 Node: minikube/192.168.99.109 Start Time: Tue, 12 Nov 2019 23:13:59 +0900 Labels: app=kubernetes-bootcamp pod-template-hash=b4d9f565 Annotations: \u0026lt;none\u0026gt; Status: Running IP: 172.17.0.11 IPs: IP: 172.17.0.11 Controlled By: ReplicaSet/kubernetes-bootcamp-b4d9f565 Containers: kubernetes-bootcamp: Container ID: docker://c8bebac0609ee166972beae7437c71733955bfde4107708d28fe3606d68e44d6 Image: jocatalin/kubernetes-bootcamp:v2 Image ID: docker-pullable://jocatalin/kubernetes-bootcamp@sha256:fb1a3ced00cecfc1f83f18ab5cd14199e30adc1b49aa4244f5d65ad3f5feb2a5 Port: \u0026lt;none\u0026gt; Host Port: \u0026lt;none\u0026gt; State: Running Started: Tue, 12 Nov 2019 23:14:00 +0900 Ready: True Restart Count: 0 Environment: \u0026lt;none\u0026gt; Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-jj2nf (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-jj2nf: Type: Secret (a volume populated by a Secret) SecretName: default-token-jj2nf Optional: false QoS Class: BestEffort Node-Selectors: \u0026lt;none\u0026gt; Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled \u0026lt;unknown\u0026gt; default-scheduler Successfully assigned default/kubernetes-bootcamp-b4d9f565-4wkhr to minikube Normal Pulled 12m kubelet, minikube Container image \u0026#34;jocatalin/kubernetes-bootcamp:v2\u0026#34; already present on machine Normal Created 12m kubelet, minikube Created container kubernetes-bootcamp Normal Started 12m kubelet, minikube Started container kubernetes-bootcamp Name: kubernetes-bootcamp-b4d9f565-fnkvz Namespace: default Priority: 0 Node: minikube/192.168.99.109 Start Time: Tue, 12 Nov 2019 23:13:59 +0900 Labels: app=kubernetes-bootcamp pod-template-hash=b4d9f565 Annotations: \u0026lt;none\u0026gt; Status: Running IP: 172.17.0.10 IPs: IP: 172.17.0.10 Controlled By: ReplicaSet/kubernetes-bootcamp-b4d9f565 Containers: kubernetes-bootcamp: Container ID: docker://896cc268b334f8241463d9c66af5c7b1accc909c86ea304e95886709b28dedf0 Image: jocatalin/kubernetes-bootcamp:v2 Image ID: docker-pullable://jocatalin/kubernetes-bootcamp@sha256:fb1a3ced00cecfc1f83f18ab5cd14199e30adc1b49aa4244f5d65ad3f5feb2a5 Port: \u0026lt;none\u0026gt; Host Port: \u0026lt;none\u0026gt; State: Running Started: Tue, 12 Nov 2019 23:14:00 +0900 Ready: True Restart Count: 0 Environment: \u0026lt;none\u0026gt; Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-jj2nf (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-jj2nf: Type: Secret (a volume populated by a Secret) SecretName: default-token-jj2nf Optional: false QoS Class: BestEffort Node-Selectors: \u0026lt;none\u0026gt; Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled \u0026lt;unknown\u0026gt; default-scheduler Successfully assigned default/kubernetes-bootcamp-b4d9f565-fnkvz to minikube Normal Pulled 12m kubelet, minikube Container image \u0026#34;jocatalin/kubernetes-bootcamp:v2\u0026#34; already present on machine Normal Created 12m kubelet, minikube Created container kubernetes-bootcamp Normal Started 12m kubelet, minikube Started container kubernetes-bootcamp Name: kubernetes-bootcamp-b4d9f565-mds8p Namespace: default Priority: 0 Node: minikube/192.168.99.109 Start Time: Tue, 12 Nov 2019 23:14:00 +0900 Labels: app=kubernetes-bootcamp pod-template-hash=b4d9f565 Annotations: \u0026lt;none\u0026gt; Status: Running IP: 172.17.0.12 IPs: IP: 172.17.0.12 Controlled By: ReplicaSet/kubernetes-bootcamp-b4d9f565 Containers: kubernetes-bootcamp: Container ID: docker://67e064117055eb3050d2fcc7aaeca90fbfa206ce17943c4746103d783e18b4cf Image: jocatalin/kubernetes-bootcamp:v2 Image ID: docker-pullable://jocatalin/kubernetes-bootcamp@sha256:fb1a3ced00cecfc1f83f18ab5cd14199e30adc1b49aa4244f5d65ad3f5feb2a5 Port: \u0026lt;none\u0026gt; Host Port: \u0026lt;none\u0026gt; State: Running Started: Tue, 12 Nov 2019 23:14:01 +0900 Ready: True Restart Count: 0 Environment: \u0026lt;none\u0026gt; Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-jj2nf (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-jj2nf: Type: Secret (a volume populated by a Secret) SecretName: default-token-jj2nf Optional: false QoS Class: BestEffort Node-Selectors: \u0026lt;none\u0026gt; Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled \u0026lt;unknown\u0026gt; default-scheduler Successfully assigned default/kubernetes-bootcamp-b4d9f565-mds8p to minikube Normal Pulled 12m kubelet, minikube Container image \u0026#34;jocatalin/kubernetes-bootcamp:v2\u0026#34; already present on machine Normal Created 12m kubelet, minikube Created container kubernetes-bootcamp Normal Started 12m kubelet, minikube Started container kubernetes-bootcamp # 直前のローリングアップデートを巻き戻す $ kubectl rollout undo deployments/kubernetes-bootcamp deployment.apps/kubernetes-bootcamp rolled back # Podの一覧を確認 # Statusが正常に戻っている $ kubectl get pods NAME READY STATUS RESTARTS AGE kubernetes-bootcamp-b4d9f565-4wkhr 1/1 Running 0 13m kubernetes-bootcamp-b4d9f565-b9t5c 1/1 Running 0 4s kubernetes-bootcamp-b4d9f565-fnkvz 1/1 Running 0 13m kubernetes-bootcamp-b4d9f565-mds8p 1/1 Running 0 13m # Podの詳細を確認 $ kubectl describe pods Name: kubernetes-bootcamp-b4d9f565-4wkhr Namespace: default Priority: 0 Node: minikube/192.168.99.109 Start Time: Tue, 12 Nov 2019 23:13:59 +0900 Labels: app=kubernetes-bootcamp pod-template-hash=b4d9f565 Annotations: \u0026lt;none\u0026gt; Status: Running IP: 172.17.0.11 IPs: IP: 172.17.0.11 Controlled By: ReplicaSet/kubernetes-bootcamp-b4d9f565 Containers: kubernetes-bootcamp: Container ID: docker://c8bebac0609ee166972beae7437c71733955bfde4107708d28fe3606d68e44d6 Image: jocatalin/kubernetes-bootcamp:v2 Image ID: docker-pullable://jocatalin/kubernetes-bootcamp@sha256:fb1a3ced00cecfc1f83f18ab5cd14199e30adc1b49aa4244f5d65ad3f5feb2a5 Port: \u0026lt;none\u0026gt; Host Port: \u0026lt;none\u0026gt; State: Running Started: Tue, 12 Nov 2019 23:14:00 +0900 Ready: True Restart Count: 0 Environment: \u0026lt;none\u0026gt; Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-jj2nf (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-jj2nf: Type: Secret (a volume populated by a Secret) SecretName: default-token-jj2nf Optional: false QoS Class: BestEffort Node-Selectors: \u0026lt;none\u0026gt; Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled \u0026lt;unknown\u0026gt; default-scheduler Successfully assigned default/kubernetes-bootcamp-b4d9f565-4wkhr to minikube Normal Pulled 16m kubelet, minikube Container image \u0026#34;jocatalin/kubernetes-bootcamp:v2\u0026#34; already present on machine Normal Created 16m kubelet, minikube Created container kubernetes-bootcamp Normal Started 16m kubelet, minikube Started container kubernetes-bootcamp Name: kubernetes-bootcamp-b4d9f565-b9t5c Namespace: default Priority: 0 Node: minikube/192.168.99.109 Start Time: Tue, 12 Nov 2019 23:27:35 +0900 Labels: app=kubernetes-bootcamp pod-template-hash=b4d9f565 Annotations: \u0026lt;none\u0026gt; Status: Running IP: 172.17.0.8 IPs: IP: 172.17.0.8 Controlled By: ReplicaSet/kubernetes-bootcamp-b4d9f565 Containers: kubernetes-bootcamp: Container ID: docker://644cf4f577f0a12f4e43d2c8ab98ab587cb231f5f9dbd41a48885c4255e146cb Image: jocatalin/kubernetes-bootcamp:v2 Image ID: docker-pullable://jocatalin/kubernetes-bootcamp@sha256:fb1a3ced00cecfc1f83f18ab5cd14199e30adc1b49aa4244f5d65ad3f5feb2a5 Port: \u0026lt;none\u0026gt; Host Port: \u0026lt;none\u0026gt; State: Running Started: Tue, 12 Nov 2019 23:27:36 +0900 Ready: True Restart Count: 0 Environment: \u0026lt;none\u0026gt; Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-jj2nf (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-jj2nf: Type: Secret (a volume populated by a Secret) SecretName: default-token-jj2nf Optional: false QoS Class: BestEffort Node-Selectors: \u0026lt;none\u0026gt; Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled \u0026lt;unknown\u0026gt; default-scheduler Successfully assigned default/kubernetes-bootcamp-b4d9f565-b9t5c to minikube Normal Pulled 2m46s kubelet, minikube Container image \u0026#34;jocatalin/kubernetes-bootcamp:v2\u0026#34; already present on machine Normal Created 2m46s kubelet, minikube Created container kubernetes-bootcamp Normal Started 2m46s kubelet, minikube Started container kubernetes-bootcamp Name: kubernetes-bootcamp-b4d9f565-fnkvz Namespace: default Priority: 0 Node: minikube/192.168.99.109 Start Time: Tue, 12 Nov 2019 23:13:59 +0900 Labels: app=kubernetes-bootcamp pod-template-hash=b4d9f565 Annotations: \u0026lt;none\u0026gt; Status: Running IP: 172.17.0.10 IPs: IP: 172.17.0.10 Controlled By: ReplicaSet/kubernetes-bootcamp-b4d9f565 Containers: kubernetes-bootcamp: Container ID: docker://896cc268b334f8241463d9c66af5c7b1accc909c86ea304e95886709b28dedf0 Image: jocatalin/kubernetes-bootcamp:v2 Image ID: docker-pullable://jocatalin/kubernetes-bootcamp@sha256:fb1a3ced00cecfc1f83f18ab5cd14199e30adc1b49aa4244f5d65ad3f5feb2a5 Port: \u0026lt;none\u0026gt; Host Port: \u0026lt;none\u0026gt; State: Running Started: Tue, 12 Nov 2019 23:14:00 +0900 Ready: True Restart Count: 0 Environment: \u0026lt;none\u0026gt; Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-jj2nf (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-jj2nf: Type: Secret (a volume populated by a Secret) SecretName: default-token-jj2nf Optional: false QoS Class: BestEffort Node-Selectors: \u0026lt;none\u0026gt; Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled \u0026lt;unknown\u0026gt; default-scheduler Successfully assigned default/kubernetes-bootcamp-b4d9f565-fnkvz to minikube Normal Pulled 16m kubelet, minikube Container image \u0026#34;jocatalin/kubernetes-bootcamp:v2\u0026#34; already present on machine Normal Created 16m kubelet, minikube Created container kubernetes-bootcamp Normal Started 16m kubelet, minikube Started container kubernetes-bootcamp Name: kubernetes-bootcamp-b4d9f565-mds8p Namespace: default Priority: 0 Node: minikube/192.168.99.109 Start Time: Tue, 12 Nov 2019 23:14:00 +0900 Labels: app=kubernetes-bootcamp pod-template-hash=b4d9f565 Annotations: \u0026lt;none\u0026gt; Status: Running IP: 172.17.0.12 IPs: IP: 172.17.0.12 Controlled By: ReplicaSet/kubernetes-bootcamp-b4d9f565 Containers: kubernetes-bootcamp: Container ID: docker://67e064117055eb3050d2fcc7aaeca90fbfa206ce17943c4746103d783e18b4cf Image: jocatalin/kubernetes-bootcamp:v2 Image ID: docker-pullable://jocatalin/kubernetes-bootcamp@sha256:fb1a3ced00cecfc1f83f18ab5cd14199e30adc1b49aa4244f5d65ad3f5feb2a5 Port: \u0026lt;none\u0026gt; Host Port: \u0026lt;none\u0026gt; State: Running Started: Tue, 12 Nov 2019 23:14:01 +0900 Ready: True Restart Count: 0 Environment: \u0026lt;none\u0026gt; Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-jj2nf (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-jj2nf: Type: Secret (a volume populated by a Secret) SecretName: default-token-jj2nf Optional: false QoS Class: BestEffort Node-Selectors: \u0026lt;none\u0026gt; Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled \u0026lt;unknown\u0026gt; default-scheduler Successfully assigned default/kubernetes-bootcamp-b4d9f565-mds8p to minikube Normal Pulled 16m kubelet, minikube Container image \u0026#34;jocatalin/kubernetes-bootcamp:v2\u0026#34; already present on machine Normal Created 16m kubelet, minikube Created container kubernetes-bootcamp Normal Started 16m kubelet, minikube Started container kubernetes-bootcamp おわり おわり.\n自分で絵を書くのがめんどくさかったけどなんとなく理解できた. 気がする.\n","date":"2019-11-14T22:57:42+09:00","permalink":"/post/2019-10-29-kubernetes-basics/","title":"Kubernetesの基本を学ぶ"},{"content":"こんにちはMinikube Minikubeをインストールしたので, 公式のチュートリアルをやってみた.\n動作環境 masOS Mojave 10.14 Minikube v1.5.0 VirtualBox 6.0.12 やったこと 公式のHello Minikubeの内容.\n日本語版もあるので今回はいちいち訳さずに気楽にやってみる.\nMinikubeクラスタの作成 Deploymentの作成 Serviceの作成 アドオンの有効化 クリーンアップ Minikubeクラスタの作成 https://kubernetes.io/docs/tutorials/hello-minikube/#create-a-minikube-cluster\nMinikubeを起動して, dashboardとやらを開いてみる.\n# Minikubeを起動する $ minikube start --vm-driver=virtualbox 😄 minikube v1.5.0 on Darwin 10.14 🔥 Creating virtualbox VM (CPUs=2, Memory=2000MB, Disk=20000MB) ... 🐳 Preparing Kubernetes v1.16.2 on Docker 18.09.9 ... 🚜 Pulling images ... 🚀 Launching Kubernetes ... ⌛ Waiting for: apiserver proxy etcd scheduler controller dns 🏄 Done! kubectl is now configured to use \u0026#34;minikube\u0026#34; ⚠️ /usr/local/bin/kubectl is version 1.14.6, and is incompatible with Kubernetes 1.16.2. You will need to update /usr/local/bin/kubectl or use \u0026#39;minikube kubectl\u0026#39; to connect with this cluster # dashboardを起動する $ minikube dashboard 🔌 Enabling dashboard ... 🤔 Verifying dashboard health ... 🚀 Launching proxy ... 🤔 Verifying proxy health ... 🎉 Opening http://127.0.0.1:53295/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/ in your default browser... 勝手にブラウザが動いてdashboardの画面が開く.\nなんかいろいろ見られそうで便利そう(小並感)\nバックグラウンド起動にできないのめんどい\u0026hellip;\nとりあえず以下はターミナルの別タブでやっていく.\nDeploymentの作成 https://kubernetes.io/docs/tutorials/hello-minikube/#create-a-deployment\nDeploymentを作る.\nDocker Quickstartでちょっと触ったけどDeploymentってなんだったっけ\u0026hellip;?(すっとぼけ)\nKubernetesのPod は、コンテナの管理やネットワーキングの目的でまとめられた、1つ以上のコンテナのグループです。このチュートリアルのPodがもつコンテナは1つのみです。Kubernetesの Deployment はPodの状態を確認し、Podのコンテナが停止した場合には再起動します。DeploymentはPodの作成やスケールを管理するために推奨される方法(手段)です。\n(https://kubernetes.io/ja/docs/tutorials/hello-minikube/#deployment%E3%81%AE%E4%BD%9C%E6%88%90 より引用)\nなるほど. なんかコンテナがいくつか集まったのがPodで, 複数台のPodをいい感じに管理するのがDeploymentだったはず.\n今回は以下のファイルで作成されたDocker imageを使ってDeploymentを作成する.\nserver.js var http = require(\u0026#39;http\u0026#39;); var handleRequest = function(request, response) { console.log(\u0026#39;Received request for URL: \u0026#39; + request.url); response.writeHead(200); response.end(\u0026#39;Hello World!\u0026#39;); }; var www = http.createServer(handleRequest); www.listen(8080); https://github.com/kubernetes/website/blob/master/content/en/examples/minikube/server.js\nDockerfile FROM node:6.14.2 EXPOSE 8080 COPY server.js . CMD node server.js https://github.com/kubernetes/website/blob/master/content/en/examples/minikube/Dockerfile\n# Deployment(hello-node)を作成する $ kubectl create deployment hello-node --image=gcr.io/hello-minikube-zero-install/hello-node deployment.apps/hello-node created # Deploymentの一覧を表示する $ kubectl get deployments NAME READY UP-TO-DATE AVAILABLE AGE hello-node 1/1 1 1 40s # Podの一覧を表示する $ kubectl get pods NAME READY STATUS RESTARTS AGE hello-node-7676b5fb8d-x7l5t 1/1 Running 0 88s # cluster eventを表示する $ kubectl get events LAST SEEN TYPE REASON OBJECT MESSAGE \u0026lt;unknown\u0026gt; Normal Scheduled pod/hello-node-7676b5fb8d-x7l5t Successfully assigned default/hello-node-7676b5fb8d-x7l5t to minikube 3m17s Normal Pulling pod/hello-node-7676b5fb8d-x7l5t Pulling image \u0026#34;gcr.io/hello-minikube-zero-install/hello-node\u0026#34; 2m38s Normal Pulled pod/hello-node-7676b5fb8d-x7l5t Successfully pulled image \u0026#34;gcr.io/hello-minikube-zero-install/hello-node\u0026#34; 2m38s Normal Created pod/hello-node-7676b5fb8d-x7l5t Created container hello-node 2m38s Normal Started pod/hello-node-7676b5fb8d-x7l5t Started container hello-node 3m17s Normal SuccessfulCreate replicaset/hello-node-7676b5fb8d Created pod: hello-node-7676b5fb8d-x7l5t 3m17s Normal ScalingReplicaSet deployment/hello-node Scaled up replica set hello-node-7676b5fb8d to 1 # kubectlの設定を表示する $ kubectl config view apiVersion: v1 clusters: - cluster: certificate-authority-data: DATA+OMITTED server: https://kubernetes.docker.internal:6443 name: docker-desktop - cluster: certificate-authority: /Users/uzimihsr/.minikube/ca.crt server: https://192.168.99.104:8443 name: minikube contexts: - context: cluster: docker-desktop user: docker-desktop name: docker-desktop - context: cluster: docker-desktop user: docker-desktop name: docker-for-desktop - context: cluster: minikube user: minikube name: minikube current-context: minikube kind: Config preferences: {} users: - name: docker-desktop user: client-certificate-data: REDACTED client-key-data: REDACTED - name: minikube user: client-certificate: /Users/uzimihsr/.minikube/client.crt client-key: /Users/uzimihsr/.minikube/client.key kubectl get eventsは初めて打った. 内部で行われている操作がわかって便利そう.\nkubectlの設定はDocker for Desktopのぶんの設定も表示しているっぽい.\n以上でDeploymentの作成ができた.\nServiceの作成 https://kubernetes.io/docs/tutorials/hello-minikube/#create-a-service\nServiceを作る.\nってかServiceってなんだっけ?(すっとぼけ)(2回目)\n通常、PodはKubernetesクラスタ内部のIPアドレスからのみアクセスすることができます。hello-nodeコンテナをKubernetesの仮想ネットワークの外部からアクセスするためには、KubernetesのServiceとしてポッドを公開する必要があります。\n(https://kubernetes.io/ja/docs/tutorials/hello-minikube/#service%E3%81%AE%E4%BD%9C%E6%88%90 より引用)\nほーん.\nPodのネットワークまわりの設定をするのがService.\n# Podをインターネット(外部)からアクセス可能にするためのServiceを作成する $ kubectl expose deployment hello-node --type=LoadBalancer --port=8080 service/hello-node exposed # Serviceの一覧を表示する $ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE hello-node LoadBalancer 10.105.200.193 \u0026lt;pending\u0026gt; 8080:32547/TCP 71s kubernetes ClusterIP 10.96.0.1 \u0026lt;none\u0026gt; 443/TCP 101m # Service(hello-node)にアクセスする $ minikube service hello-node |-----------|------------|-------------|-----------------------------| | NAMESPACE | NAME | TARGET PORT | URL | |-----------|------------|-------------|-----------------------------| | default | hello-node | | http://192.168.99.104:32547 | |-----------|------------|-------------|-----------------------------| 🎉 Opening kubernetes service default/hello-node in default browser... 勝手にブラウザが開き, server.js で定義されたHello World!の画面が表示される.\n以上でServiceの作成とPod内のコンテナへのアクセスができた.\nちなみに最初に開いたdashboardを見ると作成したリソースの情報が見られるようになっている. 便利.\nアドオンの有効化 https://kubernetes.io/docs/tutorials/hello-minikube/#enable-addons\nMinikubeに元々入ってるアドオンが使えるらしいので触ってみる.\n# 利用可能なアドオンの一覧を表示する $ minikube addons list - addon-manager: enabled - dashboard: enabled - default-storageclass: enabled - efk: disabled - freshpod: disabled - gvisor: disabled - heapster: disabled - helm-tiller: disabled - ingress: disabled - ingress-dns: disabled - logviewer: disabled - metrics-server: disabled - nvidia-driver-installer: disabled - nvidia-gpu-device-plugin: disabled - registry: disabled - registry-creds: disabled - storage-provisioner: enabled - storage-provisioner-gluster: disabled # 試しにheapster(監視ツール)を使ってみる # が, その前にkube-system(namespace)のPodとServiceの一覧を表示する $ kubectl get pod,svc -n kube-system NAME READY STATUS RESTARTS AGE pod/coredns-5644d7b6d9-d2vxs 1/1 Running 0 115m pod/coredns-5644d7b6d9-rhr9r 1/1 Running 0 115m pod/etcd-minikube 1/1 Running 0 114m pod/kube-addon-manager-minikube 1/1 Running 0 116m pod/kube-apiserver-minikube 1/1 Running 0 114m pod/kube-controller-manager-minikube 1/1 Running 0 114m pod/kube-proxy-f45zs 1/1 Running 0 115m pod/kube-scheduler-minikube 1/1 Running 0 114m pod/storage-provisioner 1/1 Running 0 115m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kube-dns ClusterIP 10.96.0.10 \u0026lt;none\u0026gt; 53/UDP,53/TCP,9153/TCP 116m # heapsterを有効化する $ minikube addons enable heapster ✅ heapster was successfully enabled # 比較のためにPodとServiceを再度確認する $ kubectl get pod,svc -n kube-system NAME READY STATUS RESTARTS AGE pod/coredns-5644d7b6d9-d2vxs 1/1 Running 0 118m pod/coredns-5644d7b6d9-rhr9r 1/1 Running 0 118m pod/etcd-minikube 1/1 Running 0 117m pod/heapster-h2d7l 1/1 Running 0 57s pod/influxdb-grafana-m6c78 2/2 Running 0 57s pod/kube-addon-manager-minikube 1/1 Running 0 118m pod/kube-apiserver-minikube 1/1 Running 0 116m pod/kube-controller-manager-minikube 1/1 Running 0 117m pod/kube-proxy-f45zs 1/1 Running 0 118m pod/kube-scheduler-minikube 1/1 Running 0 117m pod/storage-provisioner 1/1 Running 0 118m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/heapster ClusterIP 10.111.191.189 \u0026lt;none\u0026gt; 80/TCP 57s service/kube-dns ClusterIP 10.96.0.10 \u0026lt;none\u0026gt; 53/UDP,53/TCP,9153/TCP 118m service/monitoring-grafana NodePort 10.102.71.198 \u0026lt;none\u0026gt; 80:30002/TCP 58s service/monitoring-influxdb ClusterIP 10.107.121.196 \u0026lt;none\u0026gt; 8083/TCP,8086/TCP 57s # heapsterを無効化する $ minikube addons disable heapster ✅ \u0026#34;heapster\u0026#34; was successfully disabled アドオンの有効化/無効化ができた.\nなおHeapsterはDeprecatedされている模様\u0026hellip;\nクリーンアップ https://kubernetes.io/docs/tutorials/hello-minikube/#clean-up\nおかたづけする.\n# リソースの一覧を確認する $ kubectl get all NAME READY STATUS RESTARTS AGE pod/hello-node-7676b5fb8d-x7l5t 1/1 Running 0 40m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/hello-node LoadBalancer 10.105.200.193 \u0026lt;pending\u0026gt; 8080:32547/TCP 22m service/kubernetes ClusterIP 10.96.0.1 \u0026lt;none\u0026gt; 443/TCP 122m NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/hello-node 1/1 1 1 40m NAME DESIRED CURRENT READY AGE replicaset.apps/hello-node-7676b5fb8d 1 1 1 40m # Service(hello-node)を削除する $ kubectl delete service hello-node service \u0026#34;hello-node\u0026#34; deleted # Deployment(hello-node)を削除する $ kubectl delete deployment hello-node deployment.apps \u0026#34;hello-node\u0026#34; deleted # リソースを再確認する $ kubectl get all NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.96.0.1 \u0026lt;none\u0026gt; 443/TCP 125m # MinikubeのVMを停止する $ minikube stop ✋ Stopping \u0026#34;minikube\u0026#34; in virtualbox ... 🛑 \u0026#34;minikube\u0026#34; stopped. # MinikubeのVMを削除する $ minikube delete 🔥 Deleting \u0026#34;minikube\u0026#34; in virtualbox ... 💔 The \u0026#34;minikube\u0026#34; cluster has been deleted. 🔥 Successfully deleted profile \u0026#34;minikube\u0026#34; 以上, Hello Minikubeした.\nおまけ ","date":"2019-10-28T21:56:44+09:00","image":"/post/2019-10-28-hello-minikube/2019-10-28-sotochan.jpg","permalink":"/post/2019-10-28-hello-minikube/","title":"Hello Minikubeする"},{"content":"Kubernetesやるぞ 今までKubernetesの勉強用にDocker for Desktopを使ってたけど,\nなんか公式のチュートリアルがMinikubeを使ってるのでやっぱり入れてみた.\n動作環境 masOS Mojave 10.14 やったこと Minikubeのインストール Kubernetesのセットアップ Minikubeのインストール Kubernetes公式の手順(Install Minikube)をやっていく.\nまずはじめに仮想化が有効になっているかチェックする.\nVMXの文字に色がついてたらOKらしい.\n$ sysctl -a | grep -E --color \u0026#39;machdep.cpu.features|VMX\u0026#39; machdep.cpu.features: FPU VME DE PSE TSC MSR PAE MCE CX8 APIC SEP MTRR PGE MCA CMOV PAT PSE36 CLFSH DS ACPI MMX FXSR SSE SSE2 SS HTT TM PBE SSE3 PCLMULQDQ DTES64 MON DSCPL VMX SMX EST TM2 SSSE3 FMA CX16 TPR PDCM SSE4.1 SSE4.2 x2APIC MOVBE POPCNT AES PCID XSAVE OSXSAVE SEGLIM64 TSCTMR AVX1.0 RDRAND F16C kubectlはDocker for Desktopをインストールしたときに一緒に入れてるので大丈夫なはず.\n一応公式の手順はここ.\n$ kubectl version Client Version: version.Info{Major:\u0026#34;1\u0026#34;, Minor:\u0026#34;14\u0026#34;, GitVersion:\u0026#34;v1.14.6\u0026#34;, GitCommit:\u0026#34;96fac5cd13a5dc064f7d9f4f23030a6aeface6cc\u0026#34;, GitTreeState:\u0026#34;clean\u0026#34;, BuildDate:\u0026#34;2019-08-19T11:13:49Z\u0026#34;, GoVersion:\u0026#34;go1.12.9\u0026#34;, Compiler:\u0026#34;gc\u0026#34;, Platform:\u0026#34;darwin/amd64\u0026#34;} Server Version: version.Info{Major:\u0026#34;1\u0026#34;, Minor:\u0026#34;14\u0026#34;, GitVersion:\u0026#34;v1.14.6\u0026#34;, GitCommit:\u0026#34;96fac5cd13a5dc064f7d9f4f23030a6aeface6cc\u0026#34;, GitTreeState:\u0026#34;clean\u0026#34;, BuildDate:\u0026#34;2019-08-19T11:05:16Z\u0026#34;, GoVersion:\u0026#34;go1.12.9\u0026#34;, Compiler:\u0026#34;gc\u0026#34;, Platform:\u0026#34;linux/amd64\u0026#34;} また, VMのハイパーバイザーはDockerの旧Get StartedでVirtualBoxをインストール済み.\n一応ここからダウンロードできる.\nHomebrewを使ってMinikubeをインストールする.\n$ brew cask install minikube ... ==\u0026gt; Tapping homebrew/cask Cloning into \u0026#39;/usr/local/Homebrew/Library/Taps/homebrew/homebrew-cask\u0026#39;... remote: Enumerating objects: 3601, done. remote: Counting objects: 100% (3601/3601), done. remote: Compressing objects: 100% (3593/3593), done. remote: Total 3601 (delta 25), reused 576 (delta 6), pack-reused 0 Receiving objects: 100% (3601/3601), 1.20 MiB | 1.44 MiB/s, done. Resolving deltas: 100% (25/25), done. Tapped 1 command and 3489 casks (3,606 files, 3.9MB). ==\u0026gt; Satisfying dependencies All Formula dependencies satisfied. ==\u0026gt; Downloading https://storage.googleapis.com/minikube/releases/v1.5.0/minikube-darwin-amd64 ######################################################################## 100.0% ==\u0026gt; Verifying SHA-256 checksum for Cask \u0026#39;minikube\u0026#39;. ==\u0026gt; Installing Cask minikube ==\u0026gt; Creating Caskroom at /usr/local/Caskroom ==\u0026gt; We\u0026#39;ll set permissions properly so we won\u0026#39;t need sudo in the future. Password: ==\u0026gt; Linking Binary \u0026#39;minikube-darwin-amd64\u0026#39; to \u0026#39;/usr/local/bin/minikube\u0026#39;. 🍺 minikube was successfully installed! $ minikube version minikube version: v1.5.0 commit: d1151d93385a70c5a03775e166e94067791fe2d9 早速Minikubeを起動する.\n$ minikube start 😄 minikube v1.5.0 on Darwin 10.14 ✨ Automatically selected the \u0026#39;hyperkit\u0026#39; driver (alternates: [virtualbox]) 💾 Downloading driver docker-machine-driver-hyperkit: \u0026gt; docker-machine-driver-hyperkit.sha256: 65 B / 65 B [---] 100.00% ? p/s 0s \u0026gt; docker-machine-driver-hyperkit: 10.79 MiB / 10.79 MiB 100.00% 3.25 MiB p 🔑 The \u0026#39;hyperkit\u0026#39; driver requires elevated permissions. The following commands will be executed: $ sudo chown root:wheel /Users/uzimihsr/.minikube/bin/docker-machine-driver-hyperkit $ sudo chmod u+s /Users/uzimihsr/.minikube/bin/docker-machine-driver-hyperkit Password: 💿 Downloading VM boot image ... \u0026gt; minikube-v1.5.0.iso.sha256: 65 B / 65 B [--------------] 100.00% ? p/s 0s \u0026gt; minikube-v1.5.0.iso: 143.77 MiB / 143.77 MiB [-] 100.00% 58.33 MiB p/s 2s 🔥 Creating hyperkit VM (CPUs=2, Memory=2000MB, Disk=20000MB) ... 🐳 Preparing Kubernetes v1.16.2 on Docker 18.09.9 ... 💾 Downloading kubeadm v1.16.2 💾 Downloading kubelet v1.16.2 🚜 Pulling images ... 🚀 Launching Kubernetes ... ⌛ Waiting for: apiserver proxy etcd scheduler controller dns 🏄 Done! kubectl is now configured to use \u0026#34;minikube\u0026#34; ⚠️ /usr/local/bin/kubectl is version 1.14.6, and is incompatible with Kubernetes 1.16.2. You will need to update /usr/local/bin/kubectl or use \u0026#39;minikube kubectl\u0026#39; to connect with this cluster ちゃんと起動したけどなんか勝手にHyperkitが使われちゃってるので一旦止める.\n$ minikube delete 🔥 Deleting \u0026#34;minikube\u0026#34; in hyperkit ... 💔 The \u0026#34;minikube\u0026#34; cluster has been deleted. 🔥 Successfully deleted profile \u0026#34;minikube\u0026#34; 使用するハイパーバイザは--vm-driverフラグを指定すればいいらしい.\n$ minikube start --vm-driver=virtualbox 😄 minikube v1.5.0 on Darwin 10.14 🔥 Creating virtualbox VM (CPUs=2, Memory=2000MB, Disk=20000MB) ... 🐳 Preparing Kubernetes v1.16.2 on Docker 18.09.9 ... 🚜 Pulling images ... 🚀 Launching Kubernetes ... ⌛ Waiting for: apiserver proxy etcd scheduler controller dns 🏄 Done! kubectl is now configured to use \u0026#34;minikube\u0026#34; ⚠️ /usr/local/bin/kubectl is version 1.14.6, and is incompatible with Kubernetes 1.16.2. You will need to update /usr/local/bin/kubectl or use \u0026#39;minikube kubectl\u0026#39; to connect with this cluster 今度はちゃんとVirtualBoxを使ってくれた.\nよく見たらkubectlのバージョンが古くて警告されてる. バージョンを上げない場合はminikube kubectlを使う必要があるみたい.\nなんかログを見た感じmacBook上にVMを作成してそれをノードとしてKubernetesクラスタにしてるっぽい?\ndocker-machineを使ってSwarmクラスタを作ったときと似たことをやっている?\n後で余裕があったらちゃんと調べておく(たぶんやらない).\nとりあえずMinikubeのインストールは完了.\nKubernetesのセットアップ Kubernetes公式の手順(Installing Kubernetes with Minikube)のQuickstartをやっていく.\nminikube startは既に実行済みなので割愛.\n早速kubectlでサンプル用Deploymentを作成してみる.\n$ minikube kubectl create deployment hello-minikube --image=k8s.gcr.io/echoserver:1.10 Error: unknown flag: --image Usage: minikube kubectl [flags] [options] Use \u0026#34;minikube kubectl options\u0026#34; for a list of global command-line options (applies to all commands). $ kubectl create deployment hello-minikube --image=k8s.gcr.io/echoserver:1.10 deployment.apps/hello-minikube created $ kubectl get deployments NAME READY UP-TO-DATE AVAILABLE AGE hello-minikube 1/1 1 1 46s さっきの警告どおりminikube kubectl使うと--imageが使えないぞ的なエラーになった. なぜ\u0026hellip;?\nまあ普通にkubectlが使えるのであまり気にしない.\nこの hello-minikube Deploymentの8080番ポートを公開するためのServiceを作成する.\n$ kubectl expose deployment hello-minikube --type=NodePort --port=8080 service/hello-minikube exposed hello-minikube のPodを確認する.\n$ kubectl get pods NAME READY STATUS RESTARTS AGE hello-minikube-797f975945-ftf9z 1/1 Running 0 6m38s ServiceのURLを取得する.\n$ minikube service hello-minikube --url http://192.168.99.103:31394 取得したURLをブラウザで開く.\nクラスタの詳細情報が表示される.\n動作確認ができたので, ServiceとDeploymentを削除する.\n$ kubectl get all NAME READY STATUS RESTARTS AGE pod/hello-minikube-797f975945-ftf9z 1/1 Running 0 17m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/hello-minikube NodePort 10.109.38.18 \u0026lt;none\u0026gt; 8080:31394/TCP 13m service/kubernetes ClusterIP 10.96.0.1 \u0026lt;none\u0026gt; 443/TCP 29m NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/hello-minikube 1/1 1 1 17m NAME DESIRED CURRENT READY AGE replicaset.apps/hello-minikube-797f975945 1 1 1 17m $ kubectl delete service hello-minikube service \u0026#34;hello-minikube\u0026#34; deleted $ kubectl delete deployment hello-minikube deployment.apps \u0026#34;hello-minikube\u0026#34; deleted Minikubeクラスタを停止, 削除する.\n$ minikube stop ✋ Stopping \u0026#34;minikube\u0026#34; in virtualbox ... 🛑 \u0026#34;minikube\u0026#34; stopped. $ minikube delete 🔥 Deleting \u0026#34;minikube\u0026#34; in virtualbox ... 💔 The \u0026#34;minikube\u0026#34; cluster has been deleted. 🔥 Successfully deleted profile \u0026#34;minikube\u0026#34; とりあえずMinikubeでKubernetesクラスタを構築してアプリをデプロイできた.\n次はHello Minikubeあたりからチュートリアルを進めていく.\nおまけ ","date":"2019-10-27T20:48:28+09:00","image":"/post/2019-10-27-minikube/2019-10-27-sotochan.jpg","permalink":"/post/2019-10-27-minikube/","title":"Minikubeをインストールした"},{"content":"Docker Hubでイメージをシェアする Part 4ではSwarmにアプリをデプロイした.\n最後のPart 5ではイメージのシェアを行う.\nGet Started, Part 5: Sharing Images on Docker Hub https://docs.docker.com/get-started/part5/\nもくじ Prerequisites Introduction Setting Up Your Docker Hub Account Creating and Pushing to a Docker Hub Repository Conclusion Prerequisites https://docs.docker.com/get-started/part5/#prerequisites\nPart 2でのアプリのコンテナ化ができていること. Introduction https://docs.docker.com/get-started/part5/#introduction\nここまでのパートではDocker Desktopを利用してアプリのコンテナ化(Part 2), Kubernetesでのデプロイ(Part 3), Swarmでのデプロイ(Part 4)を行ってきた.\nこのパートではコンテナアプリ開発の最後のステップであるimageのシェアを行う.\nDocker Hubのようなレジストリを利用することで, imageのダウンロードと実行がどこでも簡単にできるようになる.\nSetting Up Your Docker Hub Account https://docs.docker.com/get-started/part5/#setting-up-your-docker-hub-account\nまだDocker IDを持っていない場合は以下の手順に従うことでDocker Hubを利用したimageのシェアが可能になる.\n1: ブラウザでDocker Hubの新規登録ページ https://hub.docker.com/signup を開く.\n2: フォームを埋めて送信し, Docker IDを取得する.\n3: ツールバーのDockerアイコンを押し, プルダウンメニューからSign In / Create Docker IDをクリックする.\n取得したDocker IDとパスワードを入力してログインする.\nログインに成功するとプルダウンメニューにusername: Sign outが表示される.\nまた, この手順の代わりにdocker loginでも同じことができる.\n# Dockerレジストリにログイン $ docker login Login with your Docker ID to push and pull images from Docker Hub. If you don\u0026#39;t have a Docker ID, head over to https://hub.docker.com to create one. Username: uzimihsr Password: Login Succeeded [追加]ログアウトはdocker logoutでできる.\n# Dockerレジストリからログアウト $ docker logout Removing login credentials for https://index.docker.io/v1/ Creating and Pushing to a Docker Hub Repository https://docs.docker.com/get-started/part5/#creating-and-pushing-to-a-docker-hub-repository\nDocker HubのアカウントをDocker Desktopに紐付けることができたので, さっそくリポジトリを作成して掲示板アプリをシェアする.\n1: ツールバーのDockerアイコンを押し, Repositories -\u0026gt; Create...と進む.\nデフォルトブラウザでリポジトリ作成画面が開かれる.\n2: Name(リポジトリ名)にbulletinboardと入力し, 他の内容は初期状態のまま下部のCreateを押す.\nリポジトリが作成され, bulletinboardリポジトリの画面に遷移する.\n3: imageをDocker Hubでシェアするためには, アップロードする前にDocker Hub上で管理するための名前をつける必要がある.\nDocker Hub上でimageを識別する名前は基本的に\u0026lt;Docker Hub ID\u0026gt;/\u0026lt;Repository Name\u0026gt;:\u0026lt;tag\u0026gt;の形式を取る.\n今回の掲示板アプリのimageであれば次のように名前をつける.\n# imageを確認 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE bulletinboard 1.0 06f7fe6f1ca0 2 days ago 681MB # ローカルにあるimage(bulletinboard:1.0)にDocker Hub用のタグ(uzimihsr/bulletinboard:1.0)を付け直す $ docker image tag bulletinboard:1.0 uzimihsr/bulletinboard:1.0 # imageを再確認 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE bulletinboard 1.0 06f7fe6f1ca0 2 days ago 681MB uzimihsr/bulletinboard 1.0 06f7fe6f1ca0 2 days ago 681MB 4: タグを付け直したimageをDocker Hubにアップロード(push)する.\n# image(uzimihsr/bulletinboard:1.0)をpush $ docker image push uzimihsr/bulletinboard:1.0 The push refers to repository [docker.io/uzimihsr/bulletinboard] 0b4750593321: Pushed 3896ce266caf: Pushed 439a7da7954e: Pushed f7d0b1f86348: Pushed dd883cbb0cf7: Mounted from library/node a3a3f69aebe8: Mounted from library/node 0da372da714b: Mounted from library/node bf3841becf9d: Mounted from library/node 63866df00998: Mounted from library/node 2f9128310b77: Mounted from library/node d9a5f9b8d5c2: Mounted from library/node c01c63c6823d: Mounted from library/node 1.0: digest: sha256:339f47f51b574edbd94c981e8a58b0e019ba7451a8e83f44cca1cc32d3748d8b size: 2841 先程のbulletinboardリポジトリの画面をリロードするか, https://hub.docker.com/r/uzimihsr/bulletinboard/tags を開くと確かにimageがpushされていることを確認できる. また, デフォルトではリポジトリがpublicになっていることに注意.\n[追加]レジストリのimageを利用する例は旧記事にあるので, 今回は省略する.\nConclusion https://docs.docker.com/get-started/part5/#conclusion\n以上の手順によりDocker Hub上にimageをpushできたため, どこからでもこのimageを使えるようになった.\nこのimage(bulletinboard:1.0)をローカルに持っていないクラスタから今回作成したリポジトリのimageを指定すると, Dockerはこれを自動でダウンロードして使用してくれる.\nこのようにimageはどこでも利用することができ, さらにアプリに必要な依存関係はimageの中で完結するため,\nマシンにDockerとオーケストレーター(kubernetesまたはSwarm)さえインストールすればどんなコンテナアプリでも動かすことができる.\nまた, 今回はimageをDocker Hubにpushしただけだが, imageの作成に使用したDockerfileやオーケストレーターでのコンテナの起動に使用するKubernetesYAMLファイル, Stackファイルについてはどうすれば良いのか?\nそれらのファイルはバージョン管理システム(Gitなど)でソースコードと一緒に管理し, そのリポジトリが簡単に参照できるようDocker Hubリポジトリの説明欄にリンクを貼り, imageのビルド手順を残し, アプリ利用方法がわかるようにしておくのがベストプラクティスである.\n感想 ここまで5つのパートを読んでみて, なんとなくで済ませていたDockerコンテナの扱いについてちょっとは理解が深まった気がする.\n特にレジストリを使ってimageが共有できるのは素晴らしいと思った.\nKubernetesについても(ほんとに一部のリソースだけだが)触れられて良かった.\nSwarmについてはなんか公式でもKubernetesに切り替えている感がドキュメントからも伝わってきたけど,\n旧Get Startedでも触ったように実際のマシンでも簡単にクラスタ構築ができるのは便利だとは思う\u0026hellip;\nでも最近はKubernetesも環境構築が簡単になってきているみたいなので, やっぱりお察し.\nというわけで次はKubernetesの公式ドキュメントで入門に良さそうなものがあったらまた読んでみたい.\n","date":"2019-10-15T22:40:21+09:00","permalink":"/post/2019-10-15-docker-05/","title":"Docker Quickstartを超意訳する Part 5"},{"content":"Swarmでのデプロイ Part 3ではKubernetesにアプリをデプロイした.\nPart 4では同じアプリをSwarmにデプロイしてみる.\n正直Kubernetesのほうが便利だし内容もほとんど被っているので飛ばして良かったかもしれない\u0026hellip;\nGet Started, Part 4: Deploying to Swarm https://docs.docker.com/get-started/part4/\nもくじ Prerequisites Introduction Describing Apps Using Stack Files Deploying and Checking Your Application Conclusion Prerequisites https://docs.docker.com/get-started/part4/#prerequisites\nPart 2でのアプリのコンテナ化ができていること. Docker Desktop上でSwarmが動作していること docker system infoで確認できる. Swarm: activeとなっていればOK. 動作していない場合はdocker swarm initで起動する. # Dockerのシステム情報を表示 # 長いのでSwarmを含む行を抜き出し $ docker system info | grep Swarm Swarm: active Introduction https://docs.docker.com/get-started/part4/#introduction\nこれまでのパートではアプリを単一のコンテナで起動する方法とKubernetes上にデプロイする方法を学んできたが, このパートではDocker Swarmでアプリを管理してみる.\nSwarmはKubernetesと同様にコンテナアプリのスケーリング, ネットワーク, セキュリティ, メンテナンスのための, コンテナ自体に最初から入っている機能よりも強力な様々なツールを提供する.\nPart 2で作成したコンテナアプリがSwarm上で動作することを検証するために, 今回は本番環境のSwarmクラスタではなくDocker Desktopでセットアップした開発マシンのSwarmを使用する.\nDocker Desktopで構築したSwarm環境は本番環境のクラスタで動作するものと同じ機能を持ち, 開発マシンから容易にアクセスできるという利点がある.\n[追加]Swarm(Docker社製)とKubernetes(Google社製)はどちらもコンテナを管理(オーケストレーション)するシステムであり, 正直Kubernetesのほうが強力だと思う\u0026hellip; 自分で1からセットアップする場合の触りやすさではまだSwarmに分がある?\nDescribing Apps Using Stack Files https://docs.docker.com/get-started/part4/#describing-apps-using-stack-files\nSwarmではPart 2のようにコンテナを1つずつ作成することはせず, Swarmが自動で管理するネットワーク機能を付与されたスケーラブルな複数のコンテナで構成されるserviceという単位で運用される.\nまた, 全てのSwarmオブジェクトはStackファイルと呼ばれるマニフェストファイルで定義される.\nこのファイルにはSwarmアプリを構成するコンポーネントと設定の情報が記述されていて, Swarm環境でのアプリの作成/削除に使用される.\n[追記]ここらへんの原文はほとんどPart 3と変わらない\u0026hellip; Kubernetesと機能が被っていることがよくわかる.\n1: 掲示板アプリを動かすための簡単なStackファイルをつくってみる.\n任意の場所にbb-stack.yamlというファイルを以下の内容で作成する.\nversion: \u0026#39;3.7\u0026#39; services: bb-app: image: bulletinboard:1.0 ports: - \u0026#34;8000:8080\u0026#34; このSwarmYAMLファイルでは1つのオブジェクトを定義している:\nserviceは中身が同じでスケーリングが可能な複数のコンテナを定義する.\n今回はコンテナ数が1つで(デフォルト値), そのコンテナはPart 2で作成したimage(bulletinboard:1.0)を元に起動することが記述されており, さらにホスト(開発マシン)の8000番ポートとコンテナの8080番ポートを通信させるよう設定している.\nKubernetesのServiceとSwarmのserviceは違うことに注意!\nserviceという同じ名前ではあるが, 2つのオーケストレーターではこれらはまったく違うものとして扱われる.\nSwarmではコンテナの起動/管理設定とネットワーク設定を同じ1つのserviceで定義するが,\nKubernetesではこれらはDeploymentとServiceという2つのコンポーネントで定義する.\n[追加]混同を避けるため, この意訳ではSwarmのものはservice(小文字)と表記する.\nDeploying and Checking Your Application https://docs.docker.com/get-started/part4/#deploying-and-checking-your-application\n1: アプリをSwarmにデプロイする.\n# bb-stack.yamlで定義されたStackにdemoという名前をつけてデプロイ $ docker stack deploy -c bb-stack.yaml demo Creating network demo_default Creating service demo_bb-app また, このときservice以外にStack内で使用するデフォルトのnetworkが作成される.\n2: serviceの一覧を表示して動作を確認する.\n# serviceの一覧を表示 $ docker service ls ID NAME MODE REPLICAS IMAGE PORTS po8srxqtzf13 demo_bb-app replicated 1/1 bulletinboard:1.0 *:8000-\u0026gt;8080/tcp bb-stack.yamlのserviceで定義したコンテナが1台起動しており, 開発マシンの8000番ポートがコンテナの8080番ポートに転送されていることがわかる.\n3: 開発マシンのブラウザで http://localhost:8000/ を開く.\nPart 2でコンテナを単体で動かしたときと同じ画面が表示される.\n4: 動作確認ができたら, アプリを削除する.\n# Stack(demo)を削除 $ docker stack rm demo Conclusion https://docs.docker.com/get-started/part4/#conclusion\nこのパートでは, Docker Desktopを用いて開発マシン上のSwarmに掲示板アプリをデプロイした.\nこの程度ではまだSwarmを使いこなしているとは言えないが, 最初のステップは既にクリアしている.\n環境はすでに開発マシンに構築できているので, 同じ手順でSwarmの機能をさらに活用すればアプリに他のコンポーネントを追加することもできる.\nまた, このパートではアプリのデプロイに加えてStackファイルでのアプリ定義も行った.\nアプリを動作させるために必要なすべての情報がシンプルなファイルに記述されているため, バージョンコントロール(Gitなど)で管理したり, 他の開発者と簡単に共有することができる.\nまた, Stackファイルがあればアプリを異なるクラスタに配置することも簡単に行うことができる(例: テスト環境と本番環境を分ける場合など).\n[追加]まとめの文章もPart 3とほとんど一緒. 仕事でもほとんど使わないし正直飛ばしたほうがよかったかも\u0026hellip;\n","date":"2019-10-14T13:56:32+09:00","permalink":"/post/2019-10-14-docker-04/","title":"Docker Quickstartを超意訳する Part 4"},{"content":"Kubernetesでのデプロイ Part 2ではアプリのコンテナ化を行った.\nPart 3ではKubernetesにアプリをデプロイしてみる.\nGet Started, Part 3: Deploying to Kubernetes https://docs.docker.com/get-started/part3/\nもくじ Prerequisites Introduction Describing Apps Using Kubernetes YAML Deploying and Checking Your Application Conclusion Prerequisites https://docs.docker.com/get-started/part3/#prerequisites\nPart 2でのアプリのコンテナ化ができていること. Docker Desktop上でKubernetesが動作していること 動作していない場合はPart 1の手順に従う. Introduction https://docs.docker.com/get-started/part3/#introduction\n前のパートではアプリのコンポーネントを単独のコンテナとして動作させていたが, ここからはそれらをコンテナオーケストレーター(Kubernetes)で管理する.\nKubernetesはコンテナアプリのスケーリング, ネットワーク, セキュリティ, メンテナンスのための, コンテナ自体に最初から入っている機能よりも強力な様々なツールを提供する.\n前のパートで作成したコンテナアプリがKubernetes上で動作することを検証するために, 今回は本番環境のKubernetesクラスタではなくDocker Desktopでセットアップした開発マシンのKubernetesを使用する.\nDocker Desktopで構築したKubernetes環境は本番環境のクラスタで動作するものと同じ機能を持ち, 開発マシンから容易にアクセスできるという利点がある.\nDescribing Apps Using Kubernetes YAML https://docs.docker.com/get-started/part3/#describing-apps-using-kubernetes-yaml\nKubernetesでは全てのコンテナはリソースを共有する複数のコンテナによって構成されるPodという単位で運用される.\nまた, 実際のアプリで開発者がPodを1つずつ運用することは稀で, Kubernetesが自動でスケーリングなどの管理を行う複数のPodで構成されるDeploymentという単位で運用されることが多い.\n全てのKubernetesオブジェクトはKubernetesYAMLファイルと呼ばれるマニフェストファイルで定義される.\nこのファイルにはKubernetesアプリを構成するコンポーネントと設定の情報が記述されていて, Kubernetes環境でのアプリの作成/削除に使用される.\n1: Part 1では簡単な内容のKubernetesYAMLファイルを作成したが, 今回は掲示板アプリを動かすためにもう少し詳細なものをつくってみる.\n任意の場所にbb.yamlというファイルを以下の内容で作成する.\napiVersion: apps/v1 kind: Deployment metadata: name: bb-demo namespace: default spec: replicas: 1 selector: matchLabels: bb: web template: metadata: labels: bb: web spec: containers: - name: bb-site image: bulletinboard:1.0 --- apiVersion: v1 kind: Service metadata: name: bb-entrypoint namespace: default spec: type: NodePort selector: bb: web ports: - port: 8080 targetPort: 8080 nodePort: 30001 このKuberetesYAMLファイルには---で区切られた2つのオブジェクトが記述されている:\nDeploymentは中身が同じでスケーリングが可能な複数のPodを定義する. このファイルではreplica(同じPodの数)が1つであり,\nそのPodがPart 2で作成したimage(bulletinboard:1.0)から起動するコンテナを1つ持つことが定義されている. ServiceはPodに関する通信設定を定義する. このファイルではホスト(ここでは開発マシン)の30001番ポートをPodの8080番ポートと通信させるNodePort Serviceを定義し, 掲示板アプリに接続できるようにしている. はじめはこのKubernetesYAMLファイルが長く, 複雑なものに見えるかもしれないが, 実際はどれも同じパターンで記述されている:\napiVersionでこのオブジェクトをパースするKubernetesAPIのバージョンを指定 kindでこのオブジェクトの種類を定義 metadataでオブジェクトの名前などの情報(メタデータ)を定義 specでオブジェクトのパラメータと設定を記述 Deploying and Checking Your Application https://docs.docker.com/get-started/part3/#deploying-and-checking-your-application\n1: bb.yamlがあるディレクトリに移動して, Kubernetesにアプリをデプロイする.\n# bb.yamlがあることを確認 $ ls bb.yaml # bb.yamlに記述された内容のリソースを作成 $ kubectl apply -f bb.yaml deployment.apps/bb-demo created service/bb-entrypoint created 2: 起動しているDeploymentの一覧を表示して動作を確認する.\n# Deploymentsの一覧を表示 $ kubectl get deployments NAME READY UP-TO-DATE AVAILABLE AGE bb-demo 1/1 1 1 93s bb.yamlで定義したDeployment(bb-demo)で1つのPodが正常に動作していることがわかる.\n同様にServiceについても動作確認を行う.\n# Serviceの一覧を表示 $ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE bb-entrypoint NodePort 10.102.111.61 \u0026lt;none\u0026gt; 8080:30001/TCP 6m11s kubernetes ClusterIP 10.96.0.1 \u0026lt;none\u0026gt; 443/TCP 34d Kubernetesのデフォルトで使用されるServiceの他に, bb.yamlで定義したService(bb-entrypoint)が動作し, 30001/TCPへのリクエストを受ける設定になっていることがわかる.\n3: 開発マシンのブラウザで http://localhost:30001/ を開く.\nPart 2でコンテナを単体で実行したときと同じ画面が確認できる.\n4: 動作確認が完了したら, アプリを削除する.\n# bb.yamlに記述された内容のリソースを削除 $ kubectl delete -f bb.yaml deployment.apps \u0026#34;bb-demo\u0026#34; deleted service \u0026#34;bb-entrypoint\u0026#34; deleted Conclusion https://docs.docker.com/get-started/part3/#conclusion\nこのパートでは, Docker Desktopを用いて開発マシン上のKubernetesに掲示板アプリをデプロイした.\nこの程度ではまだKubernetesを使いこなしているとは言えないが, 最初のステップは既にクリアしている.\n環境はすでに開発マシンに構築できているので, 同じ手順でKubernetesの機能をさらに活用すればアプリに他のコンポーネントを追加することもできる.\nまた, このパートではアプリのデプロイに加えてKubernetesYAMLファイルでのアプリ定義も行った.\nアプリを動作させるために必要なすべての情報がシンプルなファイルに記述されているため, バージョンコントロール(Gitなど)で管理したり, 他の開発者と簡単に共有することができる.\nまた, KubernetesYAMLファイルがあればアプリを異なるクラスタに配置することも簡単に行うことができる(例: テスト環境と本番環境を分ける場合など).\n[追加]俗に言うInfrastructure as Codeというもの. Kubernetesではアプリの実行に必要なインフラをコードとして管理することで動作環境への依存をなくしている.\n","date":"2019-10-13T22:48:55+09:00","permalink":"/post/2019-10-13-docker-03/","title":"Docker Quickstartを超意訳する Part 3"},{"content":"アプリのコンテナ化 Part 1ではDockerについて簡単な説明と環境構築を行った.\nPart 2では実際にアプリをコンテナ化してみる.\nGet Started, Part 2: Containerizing an Application https://docs.docker.com/get-started/part2/\nもくじ Prerequisites Introduction Setting Up Build and Test Your Image Conclusion Prerequisites https://docs.docker.com/get-started/part2/#prerequisites\nこのパートに入る前に, Part 1の内容を完了していること. Introduction https://docs.docker.com/get-started/part2/#introduction\nDocker Desktopのおかげでコンテナオーケストレーターが既にセットアップされているので, コンテナアプリ開発の準備はできている.\nコンテナアプリ開発は一般的に以下のフローに沿って行う.\n作成したいアプリの各コンポーネントについてimageを作成し, それを元にコンテナの作成とテストを行う. コンテナとインフラの設定を組み合わせたアプリをDocker StackファイルまたはKubernetesのYAMLファイルで定義する. コンテナ化したアプリをテスト, 共有, デプロイする. このパートでは上記フローの1つめに着目し, コンテナの元になるimageの作成を行う.\nPart 1で学んだようにimageはコンテナ化されたプロセスを動かすためのプライベートなファイルシステムを提供するものであるから, 開発者はアプリケーションの動作に必要なものだけを内包するimageを作成すれば良い.\nコンテナ化された開発環境ではアプリの依存関係をすべてimageに閉じ込めてしまうので, 開発マシンにDocker以外のものをインストールする必要がない.\nしたがって, 同じマシン上で複数のアプリを開発する際に異なるアプリ間での依存関係の競合がなくなる.\nそのため, imageの作成方法さえ取得してしまえばコンテナ化された開発環境は一般的な開発環境よりも構築が容易で使いやすい.\nSetting Up https://docs.docker.com/get-started/part2/#setting-up\n1: GitHubからサンプルプロジェクトをcloneしてくる.\n# リポジトリをclone $ git clone -b v1 https://github.com/docker-training/node-bulletin-board Cloning into \u0026#39;node-bulletin-board\u0026#39;... remote: Enumerating objects: 190, done. remote: Counting objects: 100% (190/190), done. remote: Compressing objects: 100% (132/132), done. remote: Total 190 (delta 79), reused 158 (delta 55), pack-reused 0 Receiving objects: 100% (190/190), 194.66 KiB | 432.00 KiB/s, done. Resolving deltas: 100% (79/79), done. # cloneしてきたリポジトリのbulletin-board-appディレクトリに移動 $ cd node-bulletin-board/bulletin-board-app # ファイルを確認 $ ls Dockerfile app.js fonts package.json server.js LICENSE backend index.html readme.md site.css これはNode.jsで書かれた簡単な掲示板アプリケーションで, 今回はこのアプリをコンテナ化していく.\n2: Dockerfileというファイルを見てみる. Dockerfileにはコンテナで使用するプライベートファイルシステム(image)を構築するための手順と, このimageを元にコンテナを起動する際の情報(メタデータ)が書かれている. 今回使用する掲示板アプリのDockerfileは以下の内容になっている.\n# node:6.11.5をベースimageとして使用 FROM node:6.11.5 # image内での作業ディレクトリを/usr/src/appに変更 WORKDIR /usr/src/app # 開発マシンのpackage.jsonをimageの作業ディレクトリにコピー COPY package.json . # npm(Node.jsのパッケージマネージャ)によりアプリの依存パッケージをインストール RUN npm install # 開発マシンの現在のディレクトリの中身をimageの作業ディレクトリにコピー COPY . . # コンテナが起動した際に`npm start`コマンドを実行 CMD [ \u0026#34;npm\u0026#34;, \u0026#34;start\u0026#34; ] Dockerfileを書くことはコンテナアプリ開発の第一歩である.\nDockerfileに記載された各行のコマンドは独自のimageを作成するための操作を定義し, このDockerfileの場合は次の内容を定義している:\nFROMで既に存在するimage(node:6.11.5)をベースとして指定する. node:6.11.5はNode.jsのベンダーによってビルド, Dockerチームによって検証された高品質なofficial imageであり, node 6.11.5のインタプリタと基本的な依存パッケージを内包している. WORKDIRでimage内の作業ディレクトリを**/usr/src/app**に指定する. 以降のコマンドは全てこのディレクトリで行われる. このディレクトリは開発マシンのディレクトリとは無関係である. [追加]指定したディレクトリが存在しない場合は自動で生成される. COPYで開発マシンのpackage.jsonをimageの現在のディレクトリにコピーする. 今回の場合は**/usr/src/app/package.json**に配置される. RUNでnpm installコマンドをimage内で実行する. このコマンドは現在のディレクトリにあるpackage.json(依存関係が定義されたファイル)を読み込み, アプリの依存パッケージをインストールする. COPYで開発マシンの残りのソースコードをimageの現在のディレクトリにコピーする. 以上のコマンドは一般的な開発環境でNode.jsのアプリをインストールする際とほぼ同じである.\nしかしDockerfileでこれらを定義することで, 開発マシンの環境には一切影響を与えずに必要な操作を全てimageに閉じ込めることができる.\n[追加]この開発マシンにNode.jsがなくてもimageさえあればアプリが動く. すごい.\n上記手順はファイルシステムの構築方法を定義しているが, このDockerfileにはもう1行コマンドが存在する.\nCMDはこのimageを元にコンテナが起動する際のメタデータを定義する.\nこの例では, このimageを使用して実行するプロセスがnpm startコマンドであることを示している.\n[追加]npm startはアプリを実行するコマンド.\n上記はFROMでベースとなるimageを指定し, その後にファイルシステムの構築に必要なコマンドを1行ずつ記述, 最後にメタデータを定義するというシンプルなDockerfileの良いお手本である.\nまた, この例で示したのはDockerfileで使用できるコマンドのほんの一部であり, その他のコマンドについてはDockerfile referenceで説明されている.\nBuild and Test Your Image https://docs.docker.com/get-started/part2/#build-and-test-your-image\nソースコードとDockerfileが用意できたので, いよいよimageをビルドして, それを元にコンテナが想定通り動作するか確認する.\n1: node-bulletin-board/bulletin-board-app ディレクトリにいることを確認し, 掲示板アプリのimageをビルドする.\n# 現在のディレクトリを確認 $ pwd /path/to/node-bulletin-board/bulletin-board-app # 現在のディレクトリ(.)にあるDockerfileを使用してimage(bulletinboard:1.0)をビルド $ docker image build -t bulletinboard:1.0 . ビルド時のログを見るとDockerfileに定義された操作を1行ずつ読み込んで実行し,\n作成したimageにタグ(bulletinboard:1.0)をつけていることがわかる.\n[追加]ビルド時に-tオプションでリポジトリ名:タグを指定できる.\nimageは基本的にリポジトリ名とタグで管理され, リポジトリ名はバージョンに依存しないそのimageの名前, タグはバージョンなどの情報を表す.\n(例: node:6.11.5の場合はnodeがリポジトリ名, 6.11.5がタグ)\nビルド時のログ(クリックで展開) $ docker image build -t bulletinboard:1.0 . Sending build context to Docker daemon 45.57kB Step 1/6 : FROM node:6.11.5 6.11.5: Pulling from library/node 85b1f47fba49: Pull complete ba6bd283713a: Pull complete 817c8cd48a09: Pull complete 47cc0ed96dc3: Pull complete 8888adcbd08b: Pull complete 6f2de60646b9: Pull complete 1666693bf996: Pull complete 2fe410df7942: Pull complete Digest: sha256:fe109b92edafd9821fbc1c80fd7587a1b4e1ff76fec3af675869e23e50bbf45b Status: Downloaded newer image for node:6.11.5 ---\u0026gt; 852391892b9f Step 2/6 : WORKDIR /usr/src/app ---\u0026gt; Running in 7d1414939508 Removing intermediate container 7d1414939508 ---\u0026gt; 5d0751457bd4 Step 3/6 : COPY package.json . ---\u0026gt; 50081a240492 Step 4/6 : RUN npm install ---\u0026gt; Running in 3c9f159d46c7 vue-event-bulletin@1.0.0 /usr/src/app +-- body-parser@1.19.0 | +-- bytes@3.1.0 | +-- content-type@1.0.4 | +-- debug@2.6.9 | | `-- ms@2.0.0 | +-- depd@1.1.2 | +-- http-errors@1.7.2 | | +-- inherits@2.0.3 | | `-- toidentifier@1.0.0 | +-- iconv-lite@0.4.24 | | `-- safer-buffer@2.1.2 | +-- on-finished@2.3.0 | | `-- ee-first@1.1.1 | +-- qs@6.7.0 | +-- raw-body@2.4.0 | | `-- unpipe@1.0.0 | `-- type-is@1.6.18 | +-- media-typer@0.3.0 | `-- mime-types@2.1.24 | `-- mime-db@1.40.0 +-- bootstrap@3.4.1 +-- ejs@2.7.1 +-- errorhandler@1.5.1 | +-- accepts@1.3.7 | | `-- negotiator@0.6.2 | `-- escape-html@1.0.3 +-- express@4.17.1 | +-- array-flatten@1.1.1 | +-- content-disposition@0.5.3 | +-- cookie@0.4.0 | +-- cookie-signature@1.0.6 | +-- encodeurl@1.0.2 | +-- etag@1.8.1 | +-- finalhandler@1.1.2 | +-- fresh@0.5.2 | +-- merge-descriptors@1.0.1 | +-- methods@1.1.2 | +-- parseurl@1.3.3 | +-- path-to-regexp@0.1.7 | +-- proxy-addr@2.0.5 | | +-- forwarded@0.1.2 | | `-- ipaddr.js@1.9.0 | +-- range-parser@1.2.1 | +-- safe-buffer@5.1.2 | +-- send@0.17.1 | | +-- destroy@1.0.4 | | +-- mime@1.6.0 | | `-- ms@2.1.1 | +-- serve-static@1.14.1 | +-- setprototypeof@1.1.1 | +-- statuses@1.5.0 | +-- utils-merge@1.0.1 | `-- vary@1.1.2 +-- method-override@2.3.10 +-- morgan@1.9.1 | +-- basic-auth@2.0.1 | `-- on-headers@1.0.2 +-- vue@1.0.28 | `-- envify@3.4.1 | +-- jstransform@11.0.3 | | +-- base62@1.2.8 | | +-- commoner@0.10.8 | | | +-- commander@2.20.3 | | | +-- detective@4.7.1 | | | | +-- acorn@5.7.3 | | | | `-- defined@1.0.0 | | | +-- glob@5.0.15 | | | | +-- inflight@1.0.6 | | | | | `-- wrappy@1.0.2 | | | | +-- minimatch@3.0.4 | | | | | `-- brace-expansion@1.1.11 | | | | | +-- balanced-match@1.0.0 | | | | | `-- concat-map@0.0.1 | | | | +-- once@1.4.0 | | | | `-- path-is-absolute@1.0.1 | | | +-- graceful-fs@4.2.2 | | | +-- mkdirp@0.5.1 | | | | `-- minimist@0.0.8 | | | +-- private@0.1.8 | | | +-- q@1.5.1 | | | `-- recast@0.11.23 | | | +-- ast-types@0.9.6 | | | +-- esprima@3.1.3 | | | `-- source-map@0.5.7 | | +-- esprima-fb@15001.1.0-dev-harmony-fb | | +-- object-assign@2.1.1 | | `-- source-map@0.4.4 | | `-- amdefine@1.0.1 | `-- through@2.3.8 `-- vue-resource@0.1.17 npm WARN vue-event-bulletin@1.0.0 No repository field. Removing intermediate container 3c9f159d46c7 ---\u0026gt; 64d35ff348c3 Step 5/6 : COPY . . ---\u0026gt; c8fa377b131a Step 6/6 : CMD [ \u0026#34;npm\u0026#34;, \u0026#34;start\u0026#34; ] ---\u0026gt; Running in f0aeac57fc19 Removing intermediate container f0aeac57fc19 ---\u0026gt; 06f7fe6f1ca0 Successfully built 06f7fe6f1ca0 Successfully tagged bulletinboard:1.0 [追加]ホスト(今回は開発マシン)にあるimageの一覧はdocker image lsで確認できる.\n今回の場合はbulletinboard:1.0とそのベースに使用したnode:6.11.5が存在していることがわかる.\n# imageの一覧を表示 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE bulletinboard 1.0 06f7fe6f1ca0 51 minutes ago 681MB node 6.11.5 852391892b9f 23 months ago 662MB 2: 作成したimageを使用してコンテナを起動する.\n# ホストの8000番ポートをコンテナ(bb)の8000番ポートに割り当て, # image(bulletinboard:1.0)を用いてコンテナ(bb)をデタッチドモードで起動 $ docker container run --publish 8000:8080 --detach --name bb bulletinboard:1.0 bee46e68b8cf8db5c946060f887a9a4f5bcca18e6ac6958c76d35f34d3f330b5 ここでは複数のフラグ(オプション)を組み合わせて使用している:\n--publishでホスト(この場合は開発マシン)の8000番ポートとコンテナの8000番ポートを疎通させるよう指定する. コンテナにはそれぞれ独自のポートが複数あり, ネットワークからコンテナにアクセスするためには使用するポートをこのように指定する必要がある. コンテナの指定していないポートを使う通信はデフォルトで設定されているファイアウォールによりすべて遮断される. [追加]-pオプションでも同じことができる. --detachでこのコンテナをバックグラウンドで起動するよう指定する. [追加]-dオプションでも同じ. この起動方法をデタッチドモードという. --nameでこのコンテナに名前(bb)をつける. この名前は以降のコマンドでこのコンテナを指定するときに使用できる. また, 上記のコマンドでは実行した際にコンテナでどのプロセスを実行するかの指定がされていない.\nこれはDockerfileのCMDでコンテナ起動時に自動実行するプロセス(npm start)が指定されていて, 改めて指定する必要が無いためである.\n[追加]起動中のコンテナの一覧はdocker container lsで確認できる.\n# 起動しているコンテナ一覧を表示 $ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES cc1a42172625 bulletinboard:1.0 \u0026#34;npm start\u0026#34; 5 seconds ago Up 4 seconds 0.0.0.0:8000-\u0026gt;8080/tcp bb 3: http://localhost:8000 をブラウザで開き, 掲示板アプリが実際に起動していることを確認する.\nここまで来ればアプリの動作検証に必要な任意の作業(例: 単体テストの実行)を行うことができる.\n4: 掲示板コンテナが問題なく動作することを確認したら, このコンテナを削除する.\n# コンテナ(bb)を強制的に削除 $ docker container rm --force bb bb [追加]--forceを指定するとコンテナがプロセスを実行中でも強制的にコンテナを削除することができる.\nまた, コンテナを削除せず停止だけ行いたい場合はdocker container stopを使用する. また, docker container startで停止しているコンテナを起動できる.\n# コンテナ(bb)を停止 $ docker container stop bb bb # 停止しているものも含め全てのコンテナを表示 $ docker container ls -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES cc1a42172625 bulletinboard:1.0 \u0026#34;npm start\u0026#34; 2 minutes ago Exited (0) 32 seconds ago bb # 停止しているコンテナ(bb)を起動 $ docker container start bb bb # 起動しているコンテナ一覧を表示 $ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES cc1a42172625 bulletinboard:1.0 \u0026#34;npm start\u0026#34; 6 minutes ago Up 12 seconds 0.0.0.0:8000-\u0026gt;8080/tcp bb Conclusion https://docs.docker.com/get-started/part2/#conclusion\nこのパートでは簡単なアプリケーションのコンテナ化を行い, コンテナ化したアプリが正常に動作することを確認した.\n次はKubernetes形式のYAMLファイルでコンテナの起動/管理方法を定義してKubernetes上で動作させるか(Part 3で説明), またはStackファイルを記述してこのパートと同じ内容をSwarm上で行う(Part 4で説明).\n","date":"2019-10-12T22:43:36+09:00","permalink":"/post/2019-10-12-docker-02/","title":"Docker Quickstartを超意訳する Part 2"},{"content":"Kubernetesの前にDockerだ! 仕事でコンテナオーケストレーションシステムのKubernetesに触れる機会が多い.\nしかし, その中で使われるDockerコンテナについてそもそもよくわかっておらず,\nいつもなんとなくで済ましているので公式のクイックスタートガイドを読んでみる.\nはじめに この記事は悲しみを乗り越えて書いている.\n元々読んでいたGet Startedは主にdocker-composeを用いたアプリのデプロイ手順を記述していたが,\nそれを意訳している最中でドキュメントがアップデートされ,\nこんぽーずってなんだっけ?レベルで内容が削られてしまった.\nその代わりにKuberetesを用いてアプリをデプロイする内容が増えているので,\nもう一度意訳してみる.\n読むもの Docker公式のQuickstartガイド https://docs.docker.com/get-started/ Dockerでアプリを作る手順がまとめられている 5つのパートで構成 Part 1 : Dockerの環境構築 Part 2 : イメージの作成, コンテナの実行 Part 3 : Kubernetesの環境構築とアプリのデプロイ Part 4 : Swarmの環境構築とアプリのデプロイ Part 5 : Docker Hubでのコンテナの共有 超意訳する上でのルール 以下のルールを守って訳していく.\nできるだけ元記事の流れに従って訳していく 超意訳なので翻訳の精度は保証しない 元記事に無い内容には[追加]をつける ライセンスとかよくわかんなくて怖いので画像などは自前で用意する 突然記憶喪失になっても思い出せるくらいわかりやすくする 動作環境 masOS Mojave 10.14 Docker Desktop 2.1.0.3 それでは早速Part 1からはじめる.\nGet Started, Part 1: Orientation and setup https://docs.docker.com/get-started/\nもくじ Docker concepts Images and containers Containers and virtual machines Install Docker Desktop Enable Kubernetes Enable Docker Swarm Conclusion Docker concepts https://docs.docker.com/get-started/#docker-concepts\nDockerとはコンテナでアプリをビルド, シェア, 実行するためのプラットフォームである.\nコンテナを使ってアプリをデプロイすることをコンテナ化と言う.\nコンテナ自体は前からある技術だが, Dockerはアプリを簡単にデプロイするための新しいコンテナの活用の形である.\nコンテナ化は以下の特徴のため, 広く使われるようになった.\nFlexible(柔軟) どんなに複雑なアプリでもコンテナにできる. Lightweight(軽量) コンテナはホストのカーネルを共有/活用するため, リソース面で仮想マシンより効率的である. Portable(移植が容易) コンテナはローカルでビルド, クラウドでデプロイ, どこでも実行が可能である. Loosely coupled(疎結合) それぞれのコンテナは自己完結/カプセル化されており, 他のコンテナに影響を与えずに交換/更新が可能である. Scalable(スケーリングしやすい) コンピューティング環境をまたいだコンテナの増大/分散が可能である. Secure(安全) ユーザーが特に設定しなくてもコンテナ内の各プロセスは分離され, 厳格に管理される. Images and containers https://docs.docker.com/get-started/#images-and-containers\nコンテナといっても基本的にはホストや他のコンテナから独立し, 必要なものをすべて内包した1つのプロセスに他ならない.\nここで言う独立とは各コンテナが自身のプライベートなファイルシステムを使用することを指し, そのファイルシステムはDocker imageによって提供される.\n1つのimageにはコードやバイナリ, ランタイムなどのアプリを実行するために必要な全ての要素が内包される.\nContainers and virtual machines https://docs.docker.com/get-started/#containers-and-virtual-machines\nコンテナはLinux上で動作し, ホストマシンのカーネルを他のコンテナと共有している.\nコンテナはそれぞれが独立したプロセスを実行し, ホストマシンのメモリをアプリの実行に必要なぶんだけ使用するため軽量である.\n[追加]コンテナはアプリとそれを実行するのに必要な依存関係しか含まないので軽い.\n対称的に, ホストマシン上で動作するVM(仮想マシン)は内部で普通のOSとほぼ変わらないゲストOSを動かし, 制御プログラム(スーパーバイザー)を介してマシンのリソースを使用する.\n簡単に比較すると, VMはアプリの実行に必要な分より多くのリソースを使用するため重い.\n[追加]VMはアプリと依存関係に加えてOSを含んでいるので重い. ここではハイパーバイザー型のVMについて説明されているが, ホスト型もほぼ同じ(というかホスト型のほうが重い).\nInstall Docker Desktop https://docs.docker.com/get-started/#install-docker-desktop\nコンテナの開発を始めるにはDocker Desktopを使うのが手っ取り早い.\nローカルの開発マシンにKubernetesとSwarmを簡単にセットアップしてくれて, クラスタの構築をせずにコンテナオーケストレーションの機能を使用できるので非常に便利である.\nOSX用Docker Desktopは以下よりインストールできる.\nhttps://docs.docker.com/docker-for-mac/install/\n[追加]Kubernetes, Swarmに関してはそれぞれPart3, 4で解説される.\nEnable Kubernetes https://docs.docker.com/get-started/#enable-kubernetes\nDocker DesktopはKubernetesを簡単にセットアップしてくれる.\n以下の手順で実際にセットアップと動作確認を行う.\n1: メニューバーにあるDockerのアイコンをクリックし, Preferences -\u0026gt; Kubernetesと進む.\n2: Enable Kubernetes のチェックボックスを選択し, Apply を押す.\nDocker DesktopがKubernetesのセットアップを行い, 準備が完了すると Kubernetes is running の緑色のランプが光る.\n3: Kubernetesの動作を確認するため, pod.yamlというファイルを次の内容で作成する.\napiVersion: v1 kind: Pod metadata: name: demo spec: containers: - name: testpod image: alpine:3.5 command: [\u0026#34;ping\u0026#34;, \u0026#34;8.8.8.8\u0026#34;] このpod.yamlは8.8.8.8にpingを飛ばすだけのコンテナtestpodを1つ持つdemoという名前のPodを定義している.\n[追加]PodについてはPart3で説明.\n4: pod.yamlがあるディレクトリに移動し, Podを作成する.\n# マニフェストファイル(pod.yaml)があるディレクトリで操作 $ ls pod.yaml # pod.yamlで定義されたPodを起動 $ kubectl apply -f pod.yaml pod/demo created 5: Pod(demo)が起動しているか確認する.\n# Podの一覧を表示 $ kubectl get pods NAME READY STATUS RESTARTS AGE demo 1/1 Running 0 15s 6: Pod(demo)のログを表示し, pingのログが表示されることを確認する.\n# Pod(demo)のログを確認 $ kubectl logs demo PING 8.8.8.8 (8.8.8.8): 56 data bytes [追加]自分の場合はpingが正しく返ってきていないが, とりあえずpingコマンドを実行したときのログが正しく表示されているのでここでは問題ない.\n7: 作成したPodを削除する.\n# pod.yamlで定義されたPodを削除 $ kubectl delete -f pod.yaml pod \u0026#34;demo\u0026#34; deleted [追加]kubectl delete pod demoでも同様にPodを削除できる.\nEnable Docker Swarm https://docs.docker.com/get-started/#enable-docker-swarm\nDocker DesktopはDocker Engine上で動作していて, このDocker EngineにはSwarmの動作に必要なものが最初から入っている.\n以下の手順でセットアップと動作確認を行う.\n1: DockerをSwarmモードにする.\n# Swarmクラスタを初期化 $ docker swarm init Swarm initialized: current node (gezwfuy4mutsw8sbfoqnctkez) is now a manager. To add a worker to this swarm, run the following command: docker swarm join --token SWMTKN-1-6232qe43fjsexb2wt8j0pqdo44rqt5tapg9zedin4ywt7nk4s9-evu0arm6l2e95rdn18i524mt5 192.168.65.3:2377 To add a manager to this swarm, run \u0026#39;docker swarm join-token manager\u0026#39; and follow the instructions. 2: 8.8.8.8にpingを送るService(demo)を起動する.\n[追加]ServiceについてはPart3,4で説明.\n# alpine(image)を使用して8.8.8.8にpingを送るService(demo)を起動 $ docker service create --name demo alpine:3.5 ping 8.8.8.8 5rko6rb8oe86uhadrk0ruwahb overall progress: 1 out of 1 tasks 1/1: running verify: Service converged 3: Service(demo)がコンテナを起動していることを確認する.\n# Service(demo)が起動しているコンテナの一覧を表示 $ docker service ps demo ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS ngoves00al7u demo.1 alpine:3.5 docker-desktop Running Running 18 minutes ago 4: Service(demo)のログを確認する.\n# Service(demo)のログを確認 $ docker service logs demo demo.1.ngoves00al7u@docker-desktop | PING 8.8.8.8 (8.8.8.8): 56 data bytes [追加]自分の場合はpingが正しく返ってきていないが, とりあえずpingコマンドを実行したときのログが正しく表示されているのでここでは問題ない.\n5: 作成したService(demo)を削除する.\n# Service(demo)を削除 $ docker service rm demo demo Conclusion https://docs.docker.com/get-started/#conclusion\nこのパートではDocker Desktopをインストールし, KubernetesとSwarmで簡単なコンテナを起動できることを確認した.\n次のパートでは実際にコンテナ型アプリを開発する.\n","date":"2019-10-11T23:31:23+09:00","permalink":"/post/2019-10-11-docker-01/","title":"Docker Quickstartを超意訳する Part 1"},{"content":"Dockerおさらい 前回のつづき\u0026hellip;?\n嘘だろ\u0026hellip;? Part5まで来たところでなんとGet Startedの内容が更新されてしまった.\nhttps://docs.docker.com/get-started/\n章立ても変わってPart6が消えている.\nせっかくやってきたのに残念だけど, もう一度最初から読み返すか\u0026hellip;\n今度は理解も進んでいるので, もっとうまくまとめられるようにするか\u0026hellip;\n正直読み返しても文字が多くてわかりづらかったしな\u0026hellip;\nにしても, なんでこんなタイムリーなタイミングで\u0026hellip;\nちょっとやる気が出ない.\n","date":"2019-10-10T22:54:19+09:00","permalink":"/post/2019-10-10-docker-get-started-05/","title":"読んでる途中でDocker Get Startedが更新されちゃった話"},{"content":"Dockerおさらい 前回のつづき\nDeprecated 公式のGet Startedが更新されてしまったので,\nこの記事の内容は古くなっている. 非推奨.\n読んだもの Get Started, Part 4: Swarms\n複数のマシンでDockerを稼働させるためのswarmに関する内容.\n事前準備 このパートに入る前に以下の条件をクリアすること.\nバージョン1.13以降のDockerがインストール済みであること Part 3で説明したDocker Composeが入っていること Docker Machineが入っていること Docker Desktop for Macには入ってるので大丈夫 Part 1, Part 2の内容を理解していること Part2で作成したイメージがDocker Hubにアップロードされていて, 正常に動くこと Part3で作成したdocker-compose.ymlがあること はじめに このパートでは複数台のマシンで構成されるswarmクラスタにアプリをデプロイする.\nswarmクラスタとは複数のマシンをDocker化し1つにしたもので, 複数クラスタ, 複数マシンで動作するアプリケーションを実現するものである.\nswarmクラスタを理解する swarmとは:\nDockerが稼働する複数台のマシンが1つのクラスタにまとまったもの swarmを構成するマシンはノードと呼ばれる swarmを構成するマシンは物理マシン/VMを問わない swarmマネージャとワーカーで構成される swarmマネージャ 1つのswarmに1台だけ存在するノード dockerコマンドを実行する 他のマシン(ノード)をワーカーとして管理/指示を行う役割 コンテナを実行するときの方針を定める役割 方針はdocker-compose.ymlで定義できる ワーカー swarmマネージャ以外のノード リソースを提供する 他のワーカーへ指示を出すことはできない これまではシングルホストモードでDockerを使用してきたが, このパートではswarmモードでDockerを使用していく.\nswarmモードでは操作中のローカルマシンではなく, クラスタ単位でDockerのコマンドが実行されるようになる\nswarmをセットアップする 上記の通りswarmは物理/VMを問わない複数のノード(マシン)で構成されており,\n1つのマシンでdocker swarm initを実行するとdockerがswarmモードになり, そのマシンがswarmマネージャになる.\n他のマシンでdocker swarm joinを実行するとそのマシンがswarmクラスタにワーカーとして登録される.\nここでは例として2台のVMでswarmクラスタを構成してみる.\nクラスタを作成する まずは手元のマシンでVMを建てる準備をする.\n自分の場合はMacなのでVirtualBoxをインストールした.\ndocker-machineコマンドを使ってDocker用のVMを2台作成する.\n今回はmyvm1, myvm2という名前のVMを作成する.\n$ docker-machine create --driver virtualbox myvm1 $ docker-machine create --driver virtualbox myvm2 作成したVM(docker-machine)のIPアドレスなどの情報はdocker-machine lsコマンドで確認できる.\n$ docker-machine ls NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS myvm1 - virtualbox Running tcp://192.168.99.100:2376 v18.09.9 myvm2 - virtualbox Running tcp://192.168.99.101:2376 v18.09.9 1台目のVM(myvm1)はswarmマネージャとして, 2台目(myvm2)はワーカーとして使用する.\nVM(docker-machine)上でコマンドを実行するにはdocker-machine sshコマンドを利用する.\nここではmyvm1にdocker swarm initコマンドを実行させてswarmマネージャにする.\n$ docker-machine ssh myvm1 \u0026#34;docker swarm init --advertise-addr 192.168.99.100\u0026#34; Swarm initialized: current node (zfpm1vq86ladj9dn5cs5pdg7x) is now a manager. To add a worker to this swarm, run the following command: docker swarm join --token SWMTKN-1-1gpfpimuxhtsz3i8mywpwb3lmsfwzldgpq7h70iuapkuq83id6-27xtpkdnuf42c7uwu6xrmz9sz 192.168.99.100:2377 To add a manager to this swarm, run \u0026#39;docker swarm join-token manager\u0026#39; and follow the instructions. docker swarm initコマンドの出力結果を見ると, このswarmに新たにワーカーを追加するためのコマンドが表示されている.\nこれに従って, myvm2をこのswarmに追加する.\n$ docker-machine ssh myvm2 \u0026#34;docker swarm join --token SWMTKN-1-1gpfpimuxhtsz3i8mywpwb3lmsfwzldgpq7h70iuapkuq83id6-27xtpkdnuf42c7uwu6xrmz9sz 192.168.99.100:2377\u0026#34; This node joined a swarm as a worker. これでswarmが構築できた.\n試しにswarmマネージャ(myvm1)からdocker node lsコマンドを使用するとこのswarmに存在するノードの一覧が表示される.\n$ docker-machine ssh myvm1 \u0026#34;docker node ls\u0026#34; ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION zfpm1vq86ladj9dn5cs5pdg7x * myvm1 Ready Active Leader 18.09.9 2t7nqlaes8gl08swfe5d81lim myvm2 Ready Active 18.09.9 アプリをswarmクラスタにデプロイする 難しいのはここまで.\nここからはpart3と同様の手順を進めていく.\nただしdockerコマンドはswarmマネージャ(myvm1)からのみ実行可能であることに注意.\ndocker-machineコマンドを実行するシェルをswarmマネージャに合わせて設定する ここまでdockerコマンドを実行するためには手元のシェルでdocker-machine sshを使用してswarmマネージャ(myvm1)にコマンドを送信していた.\nこのままでも問題ないが, 手元のdocker-compose.ymlを使いたい場合に少し面倒になる.\nそのため, 手元のシェルをmyvm1のDockerデーモンと疎通させる設定を行う.\n手元のシェルでdocker-machine env myvm1コマンドを実行するとmyvm1と疎通するためのコマンドが出力されるので, これを貼り付けるだけで設定が完了する.\n$ docker-machine env myvm1 export DOCKER_TLS_VERIFY=\u0026#34;1\u0026#34; export DOCKER_HOST=\u0026#34;tcp://192.168.99.100:2376\u0026#34; export DOCKER_CERT_PATH=\u0026#34;/Users/uzimihsr/.docker/machine/machines/myvm1\u0026#34; export DOCKER_MACHINE_NAME=\u0026#34;myvm1\u0026#34; # Run this command to configure your shell: # eval $(docker-machine env myvm1) $ eval $(docker-machine env myvm1) この状態でswarmのノード確認コマンドdocker node lsを実行すると, myvm1にssh経由で実行した際と同じ出力が得られる.\n$ docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION zfpm1vq86ladj9dn5cs5pdg7x * myvm1 Ready Active Leader 18.09.9 2t7nqlaes8gl08swfe5d81lim myvm2 Ready Active 18.09.9 ちなみに別のシェルを開いて疎通の設定をせずに同じコマンドを打つと当然ながらswarmマネージャとして振る舞えないのでエラーになる.\n$ docker node ls Error response from daemon: This node is not a swarm manager. Use \u0026#34;docker swarm init\u0026#34; or \u0026#34;docker swarm join\u0026#34; to connect this node to swarm and try again. swarmマネージャからアプリをデプロイする シェルの設定が完了し, 直接swarmマネージャ(myvm1)でコマンドが実行できるようになったのでいよいよアプリをデプロイする.\nPart3と同じdocker-compose.ymlが手元にあることを確認し, docker stack deployコマンドを実行する.\n$ ls docker-compose.yml $ docker stack deploy -c ./docker-compose.yml getstartedlab Creating network getstartedlab_webnet Creating service getstartedlab_web これでアプリがデプロイされた. すごい簡単.\nPart3と同様にタスクの確認を行う.\n$ docker stack ps getstartedlab ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS ikvg0oxrqbc7 getstartedlab_web.1 uzimihsr/get-started:part2 myvm1 Running Running 3 minutes ago uy3juv1txuon getstartedlab_web.2 uzimihsr/get-started:part2 myvm2 Running Running 3 minutes ago j6545cpwqmvl getstartedlab_web.3 uzimihsr/get-started:part2 myvm2 Running Running 3 minutes ago Part3で確認したものと同じく3つのサービスが表示される(元々は5個だがPart3の後半で3個にスケーリングしている).\nまた, 注目すべきはNODEの列で, サービスがそれぞれmyvm1とmyvm2に分かれて稼働していることがわかる.\nクラスタにアクセスする デプロイされたアプリにはmyvm1, myvm2両方のIPアドレスでアクセスできる.\n$ docker-machine ls NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS myvm1 * virtualbox Running tcp://192.168.99.100:2376 v18.09.9 myvm2 - virtualbox Running tcp://192.168.99.101:2376 v18.09.9 curlで何度か叩いてみる.\n今回使用するアプリはdockerが稼働するマシンの4000番ポートをコンテナの80番ポートに割り当てていることに注意.\n$ curl http://192.168.99.100:4000 \u0026lt;h3\u0026gt;Hello World!\u0026lt;/h3\u0026gt;\u0026lt;b\u0026gt;Hostname:\u0026lt;/b\u0026gt; bf3c2f00e239\u0026lt;br/\u0026gt;\u0026lt;b\u0026gt;Visits:\u0026lt;/b\u0026gt; \u0026lt;i\u0026gt;cannot connect to Redis, counter disabled\u0026lt;/i\u0026gt; $ curl http://192.168.99.100:4000 \u0026lt;h3\u0026gt;Hello World!\u0026lt;/h3\u0026gt;\u0026lt;b\u0026gt;Hostname:\u0026lt;/b\u0026gt; df6c4458369c\u0026lt;br/\u0026gt;\u0026lt;b\u0026gt;Visits:\u0026lt;/b\u0026gt; \u0026lt;i\u0026gt;cannot connect to Redis, counter disabled\u0026lt;/i\u0026gt; $ curl http://192.168.99.100:4000 \u0026lt;h3\u0026gt;Hello World!\u0026lt;/h3\u0026gt;\u0026lt;b\u0026gt;Hostname:\u0026lt;/b\u0026gt; 0541e646a6ce\u0026lt;br/\u0026gt;\u0026lt;b\u0026gt;Visits:\u0026lt;/b\u0026gt; \u0026lt;i\u0026gt;cannot connect to Redis, counter disabled\u0026lt;/i\u0026gt; $ curl http://192.168.99.101:4000 \u0026lt;h3\u0026gt;Hello World!\u0026lt;/h3\u0026gt;\u0026lt;b\u0026gt;Hostname:\u0026lt;/b\u0026gt; df6c4458369c\u0026lt;br/\u0026gt;\u0026lt;b\u0026gt;Visits:\u0026lt;/b\u0026gt; \u0026lt;i\u0026gt;cannot connect to Redis, counter disabled\u0026lt;/i\u0026gt; $ curl http://192.168.99.101:4000 \u0026lt;h3\u0026gt;Hello World!\u0026lt;/h3\u0026gt;\u0026lt;b\u0026gt;Hostname:\u0026lt;/b\u0026gt; bf3c2f00e239\u0026lt;br/\u0026gt;\u0026lt;b\u0026gt;Visits:\u0026lt;/b\u0026gt; \u0026lt;i\u0026gt;cannot connect to Redis, counter disabled\u0026lt;/i\u0026gt; $ curl http://192.168.99.101:4000 \u0026lt;h3\u0026gt;Hello World!\u0026lt;/h3\u0026gt;\u0026lt;b\u0026gt;Hostname:\u0026lt;/b\u0026gt; 0541e646a6ce\u0026lt;br/\u0026gt;\u0026lt;b\u0026gt;Visits:\u0026lt;/b\u0026gt; \u0026lt;i\u0026gt;cannot connect to Redis, counter disabled\u0026lt;/i\u0026gt; 負荷分散で3種類のホストがランダムに呼び出されているのが確認できる.\nmyvm1, myvm2どちらのIPアドレスでも同じように動作するのは, swarmのノードがingressルーティングメッシュに属しているためである.\n(超意訳)今回のようにdocker-compose.ymlでマシンの4000番ポートをコンテナの80番ポートに割り当てる設定をした場合, 各ノード自身が4000番ポートをswarmでデプロイされたサービスのために確保する. また, 各ノードの4000番ポートにはロードバランサーが割り当てられ, このロードバランサーはswarm内でノードに関係なく全てのコンテナに対して負荷分散を行う.\nアプリの繰り返しとスケーリング Part3と同様に, docker-compose.ymlを編集することでアプリのスケーリングを行うことができる.\nまた, この状態からswarmにノードを増やすこともできる.\n今回はmyvm3を作成し, myvm2と同様にワーカーとしてswarmに追加する.\nさらにコンテナ数を現在の3から8に増やしてみる.\n$ docker-machine create --driver virtualbox myvm3 $ docker-machine ssh myvm3 \u0026#34;docker swarm join --token SWMTKN-1-1gpfpimuxhtsz3i8mywpwb3lmsfwzldgpq7h70iuapkuq83id6-27xtpkdnuf42c7uwu6xrmz9sz 192.168.99.100:2377\u0026#34; This node joined a swarm as a worker. $ docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION zfpm1vq86ladj9dn5cs5pdg7x * myvm1 Ready Active Leader 18.09.9 2t7nqlaes8gl08swfe5d81lim myvm2 Ready Active 18.09.9 p4n9y3kez6u35fkduojjloa17 myvm3 Ready Active 19.03.3 $ vim docker-compose.yml $ docker stack deploy -c ./docker-compose.yml getstartedlab $ docker stack ps getstartedlab ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS ikvg0oxrqbc7 getstartedlab_web.1 uzimihsr/get-started:part2 myvm1 Running Running 23 hours ago uy3juv1txuon getstartedlab_web.2 uzimihsr/get-started:part2 myvm2 Running Running 23 hours ago j6545cpwqmvl getstartedlab_web.3 uzimihsr/get-started:part2 myvm2 Running Running 23 hours ago hax39564wx4d getstartedlab_web.4 uzimihsr/get-started:part2 myvm3 Running Running 23 seconds ago t9qx3iyptfi7 getstartedlab_web.5 uzimihsr/get-started:part2 myvm1 Running Running 33 seconds ago 4e84bwdkq8hj getstartedlab_web.6 uzimihsr/get-started:part2 myvm1 Running Running 33 seconds ago vpf7nwgvonlk getstartedlab_web.7 uzimihsr/get-started:part2 myvm2 Running Running 34 seconds ago aca3i7e0f615 getstartedlab_web.8 uzimihsr/get-started:part2 myvm3 Running Running 23 seconds ago レプリカ数を変更したdocker-compose.yml version: \u0026#34;3\u0026#34; services: web: image: uzimihsr/get-started:part2 deploy: # イメージインスタンスの数を8に指定 replicas: 8 resources: limits: cpus: \u0026#34;0.1\u0026#34; memory: 50M restart_policy: condition: on-failure ports: - \u0026#34;4000:80\u0026#34; networks: - webnet networks: webnet: コンテナ数が増え, 新たに追加したワーカー(myvm3)にもサービスがデプロイされている.\n以上, 簡単にノードの追加, アプリのスケーリングができることがわかった.\nクリーンアップと再起動 アプリの削除と再起動も簡単に行える.\nスタックとswarm スタックはdocker stack rmコマンドで削除できる.\n$ docker stack rm getstartedlab Removing service getstartedlab_web Removing network getstartedlab_webnet 各ノードでdocker swarm leaveコマンドを使用するとそのノードを現在のswarmから開放することができる. が, Part5で使用するので現時点でswarmの解体は行わない.\ndocker-machineシェルの変数を解除 swarmマネージャ(myvm1)と疎通してコマンドを実行するために設定したシェルは次のように設定を解除することができる.\n$ eval $(docker-machine env -u) $ docker node ls Error response from daemon: This node is not a swarm manager. Use \u0026#34;docker swarm init\u0026#34; or \u0026#34;docker swarm join\u0026#34; to connect this node to swarm and try again. docker-machineを再起動する VM(docker-machine)はローカルマシンを停止するか, docker-machine stopコマンドで停止することができる.\n今回は全部のVMを停止してみる.\n$ docker-machine stop $(docker-machine ls -q) Stopping \u0026#34;myvm3\u0026#34;... Stopping \u0026#34;myvm1\u0026#34;... Stopping \u0026#34;myvm2\u0026#34;... Machine \u0026#34;myvm1\u0026#34; was stopped. Machine \u0026#34;myvm3\u0026#34; was stopped. Machine \u0026#34;myvm2\u0026#34; was stopped. $ docker-machine ls NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS myvm1 - virtualbox Stopped Unknown myvm2 - virtualbox Stopped Unknown myvm3 - virtualbox Stopped Unknown また, 停止したVMはdocker-machine startコマンドで再起動できる.\n$ docker-machine start myvm1 $ docker-machine ls NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS myvm1 - virtualbox Running tcp://192.168.99.100:2376 v18.09.9 myvm2 - virtualbox Stopped Unknown myvm3 - virtualbox Stopped Unknown $ docker-machine start $(docker-machine ls -q) $ docker-machine ls NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS myvm1 - virtualbox Running tcp://192.168.99.100:2376 v18.09.9 myvm2 - virtualbox Running tcp://192.168.99.101:2376 v18.09.9 myvm3 - virtualbox Running tcp://192.168.99.102:2376 v19.03.3 感想(まとめ) swarmに関する内容がもりもりだった.\n個人的にはdocker-machineを利用するとdockerをインストールしたVMが簡単に立てられて便利だと思った. VirtualBoxすごい.\n要点としては,\nswarmを利用すると複数台のマシンのリソースを利用してDockerが動かせること swarmはマネージャとワーカーの2種類のノードで構成されること ワーカーは計算リソースを提供するのみでコンテナやノードの操作は行わない マネージャはワーカーの管理を行い, これまでローカルマシンで実行していたdockerコマンドを使用してswarmクラスタにアプリをデプロイ/スケーリングする docker-machineを利用してローカルマシン1台の中に複数のノード用VMを簡単に作成できること 複数のノードで構成される1つのswarmはまるで1つのマシンのように扱うことができ, 実際にどのノードでコンテナが動くかに関係なくswarm内でロードバランシングが行えること がわかれば十分.\n次はGet Started, Part 5: Stacksを読む.\nそろそろ疲れてきた.\n","date":"2019-10-09T22:14:07+09:00","permalink":"/post/2019-10-07-docker-get-started-04/","title":"[Deprecated]Docker Get Startedを読む Part4"},{"content":"Dockerおさらい 前回のつづき\nDeprecated 公式のGet Startedが更新されてしまったので,\nこの記事の内容は古くなっている. 非推奨.\n読んだもの Get Started, Part 3: Services\nコンテナを実際に使うためのサービスに関する内容.\n事前準備 このパートに入る前に以下の条件をクリアすること.\nバージョン1.13以降のDockerがインストール済みであること Docker Composeが入っていること Docker Desktop for Macには入ってるので大丈夫 Part 1, Part 2の内容を理解していること Part2で作成したイメージがDocker Hubにアップロードされていて, 正常に動くこと はじめに このパートではDockerにおけるアプリ開発の3つの段階のうち,\nアプリのスケーリングとロードバランシング(負荷分散)を行うサービスについて説明する.\nサービスとは 分散アプリケーションを構成するそれぞれのアプリケーション 例:動画共有サイト(分散アプリケーション) DBに動画を保存するサービス(アプリ) アップロードされた動画をエンコードするサービス(アプリ) フロントエンド用のサービス(アプリ) 本番環境のコンテナ あくまで1つのイメージを使用 イメージ(コンテナ)の実行方法を定義するもの 使用するポートの指定 サービスの規模に合わせたコンテナのレプリカ数の指定など (超意訳)イメージの使い方を細かく設定した実用性の高いコンテナのラッパー? docker-compose.ymlで定義する はじめてのdocker-compose.yml docker-compose.ymlはYAML形式でコンテナの動作を定義するファイルである.\ndocker-compose.yml 実際に作ってみる.\nどのディレクトリでもいいのでdocker-compose.ymlを以下の内容で作成する.\n$ mkdir workspace $ cd workspace $ vim docker-compose.yml docker-compose.yml version: \u0026#34;3\u0026#34; services: # webという名前のサービスを定義 web: # Part2でDocker Hubに登録したイメージを指定 image: uzimihsr/get-started:part2 deploy: # イメージインスタンスの数を5に指定 replicas: 5 resources: limits: # 各インスタンスのCPU性能を10%に制限 cpus: \u0026#34;0.1\u0026#34; # 各インスタンスのメモリを50MBに制限 memory: 50M restart_policy: # コンテナが停止した場合すぐに再起動するよう設定 condition: on-failure ports: # ローカルマシンの4000番ポートをwebの80番に割り当て - \u0026#34;4000:80\u0026#34; networks: # webnetという名前のネットワークでポートを共有するよう設定 - webnet networks: # webnetという名前のネットワークを定義 # 何も指定しない場合は負荷分散ネットワークになる webnet: ざっくり説明するとこのdocker-compose.ymlは\nwebという名前のサービスを定義 Docker Hubのイメージを使用する コンテナレプリカは5個稼働させる 各インスタンス(レプリカ?)のCPUはシステムの10%, メモリは50MBに設定 コンテナが死んだら再起動させる ホスト(Dockerを起動しているマシン)の4000番ポートをwebサービスの80番ポートに割り当てる webnetというネットワークを使用する webnetという名前のネットワークを定義 負荷分散ネットワーク という設定を行っている.\nロードバランスしたアプリを動かす docker-compose.ymlの内容を実行するため, まずはswarmクラスタを初期化する.\nswarmについてはPart 4で説明するので, 今はおまじないだと思って大丈夫.\n$ docker swarm init Swarm initialized: current node (hw3rcr1q9vlpp3qgfw959knwb) is now a manager. それではdocker stack deployコマンドを使用して実際にアプリを立ち上げる.\n-c FILE_PATHオプションで使用するdocker-compose.ymlへのパスを指定できる.\n今回は現在のディレクトリにあるdocker-compose.ymlを指定して, getstartedlabという名前のアプリ(スタック)を起動する.\n$ ls docker-compose.yml $ docker stack deploy -c docker-compose.yml getstartedlab Creating network getstartedlab_webnet Creating service getstartedlab_web 実際に起動したサービスの一覧はdocker service lsで確認できる.\nまたは, docker stack services getstartedlabでもgetstartedlabスタックに関連するサービスの一覧が表示される.\n今回はgetstartedlabスタックの中でgetstartedlab_webというサービスが作成されていることが確認できる.\n$ docker service ls ID NAME MODE REPLICAS IMAGE PORTS bggiqgkl98zv getstartedlab_web replicated 5/5 uzimihsr/get-started:part2 *:4000-\u0026gt;80/tcp $ docker stack services getstartedlab ID NAME MODE REPLICAS IMAGE PORTS bggiqgkl98zv getstartedlab_web replicated 5/5 uzimihsr/get-started:part2 *:4000-\u0026gt;80/tcp また, サービスの中で起動されているコンテナはタスクと呼ばれ, それぞれにユニークなIDが振られて管理される.\n今回はdocker service psコマンドでgetstartedlab_webサービスのタスクを確認する.\nまたは, 他のコンテナが何も起動していない場合に限りdocker container ls -qコマンドでコンテナIDの一覧を取得することができる.\n(タスクのIDとは違うことに注意)\n$ docker service ps getstartedlab_web ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS p8z32hd4620k getstartedlab_web.1 uzimihsr/get-started:part2 docker-desktop Running Running 8 minutes ago x6qtw0s78rl4 getstartedlab_web.2 uzimihsr/get-started:part2 docker-desktop Running Running 8 minutes ago d5u6qaw9y74a getstartedlab_web.3 uzimihsr/get-started:part2 docker-desktop Running Running 8 minutes ago qmhi68ujtazx getstartedlab_web.4 uzimihsr/get-started:part2 docker-desktop Running Running 8 minutes ago j692c5bbtilh getstartedlab_web.5 uzimihsr/get-started:part2 docker-desktop Running Running 8 minutes ago $ docker container ls -q 0d3641cc5d70 3fccce9c79e8 7ec5196a0211 374fec7c58a7 152251abda79 ロードバランシング(負荷分散)が行われているか確認する.\nブラウザでも良いが, 今回はcurlで何回かURLを叩いてみる.\n$ curl http://localhost:4000 \u0026lt;h3\u0026gt;Hello World!\u0026lt;/h3\u0026gt;\u0026lt;b\u0026gt;Hostname:\u0026lt;/b\u0026gt; 374fec7c58a7\u0026lt;br/\u0026gt;\u0026lt;b\u0026gt;Visits:\u0026lt;/b\u0026gt; \u0026lt;i\u0026gt;cannot connect to Redis, counter disabled\u0026lt;/i\u0026gt; $ curl http://localhost:4000 \u0026lt;h3\u0026gt;Hello World!\u0026lt;/h3\u0026gt;\u0026lt;b\u0026gt;Hostname:\u0026lt;/b\u0026gt; 3fccce9c79e8\u0026lt;br/\u0026gt;\u0026lt;b\u0026gt;Visits:\u0026lt;/b\u0026gt; \u0026lt;i\u0026gt;cannot connect to Redis, counter disabled\u0026lt;/i\u0026gt; $ curl http://localhost:4000 \u0026lt;h3\u0026gt;Hello World!\u0026lt;/h3\u0026gt;\u0026lt;b\u0026gt;Hostname:\u0026lt;/b\u0026gt; 0d3641cc5d70\u0026lt;br/\u0026gt;\u0026lt;b\u0026gt;Visits:\u0026lt;/b\u0026gt; \u0026lt;i\u0026gt;cannot connect to Redis, counter disabled\u0026lt;/i\u0026gt; $ curl http://localhost:4000 \u0026lt;h3\u0026gt;Hello World!\u0026lt;/h3\u0026gt;\u0026lt;b\u0026gt;Hostname:\u0026lt;/b\u0026gt; 7ec5196a0211\u0026lt;br/\u0026gt;\u0026lt;b\u0026gt;Visits:\u0026lt;/b\u0026gt; \u0026lt;i\u0026gt;cannot connect to Redis, counter disabled\u0026lt;/i\u0026gt; $ curl http://localhost:4000 \u0026lt;h3\u0026gt;Hello World!\u0026lt;/h3\u0026gt;\u0026lt;b\u0026gt;Hostname:\u0026lt;/b\u0026gt; 152251abda79\u0026lt;br/\u0026gt;\u0026lt;b\u0026gt;Visits:\u0026lt;/b\u0026gt; \u0026lt;i\u0026gt;cannot connect to Redis, counter disabled\u0026lt;/i\u0026gt; 注目すべきはHostnameの部分で, アクセスする度にホストが変わっていることから負荷分散(ラウンドロビン方式)が正常に行われていることがわかる.\ngetstartedlabスタック内のタスクはdocker stack ps getstartedlabで確認できるが,\n今回はgetstartedlab_webサービスしか動いていないため,\n先程docker service psで確認したのと同じタスクの一覧が表示される.\n$ docker stack ps getstartedlab ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS p8z32hd4620k getstartedlab_web.1 uzimihsr/get-started:part2 docker-desktop Running Running 22 minutes ago x6qtw0s78rl4 getstartedlab_web.2 uzimihsr/get-started:part2 docker-desktop Running Running 22 minutes ago d5u6qaw9y74a getstartedlab_web.3 uzimihsr/get-started:part2 docker-desktop Running Running 22 minutes ago qmhi68ujtazx getstartedlab_web.4 uzimihsr/get-started:part2 docker-desktop Running Running 22 minutes ago j692c5bbtilh getstartedlab_web.5 uzimihsr/get-started:part2 docker-desktop Running Running 22 minutes ago アプリをスケーリングする docker-compose.ymlのservices.web.deploy.replicasの値を変更することでアプリのスケーリングができる.\n以下の手順でgetstartedlabスタックを更新してみる.\nなお, 更新前にスタックを停止したりコンテナを削除する必要はない(自動でやってくれる).\n$ vim docker-compose.yml $ docker stack deploy -c docker-compose.yml getstartedlab Updating service getstartedlab_web (id: bggiqgkl98zvy8ayiqmwxyt79) レプリカ数を変更したdocker-compose.yml version: \u0026#34;3\u0026#34; services: web: image: uzimihsr/get-started:part2 deploy: # イメージインスタンスの数を3に指定 replicas: 3 resources: limits: cpus: \u0026#34;0.1\u0026#34; memory: 50M restart_policy: condition: on-failure ports: - \u0026#34;4000:80\u0026#34; networks: - webnet networks: webnet: 再びgetstartedlabスタックのタスクを確認してみると, タスクが減っている(今回は3を指定した)ことが確認できる.\n$ docker stack ps getstartedlab ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS p8z32hd4620k getstartedlab_web.1 uzimihsr/get-started:part2 docker-desktop Running Running 35 minutes ago x6qtw0s78rl4 getstartedlab_web.2 uzimihsr/get-started:part2 docker-desktop Running Running 35 minutes ago d5u6qaw9y74a getstartedlab_web.3 uzimihsr/get-started:part2 docker-desktop Running Running 35 minutes ago 余談だが, docker inspectコマンドでコンテナ(タスク)の詳細を確認することもできる.\ndocker inspect $ docker inspect p8z32hd4620k [ { \u0026#34;ID\u0026#34;: \u0026#34;p8z32hd4620kaipbv9uzlte2j\u0026#34;, \u0026#34;Version\u0026#34;: { \u0026#34;Index\u0026#34;: 22 }, \u0026#34;CreatedAt\u0026#34;: \u0026#34;2019-10-06T09:01:36.6249845Z\u0026#34;, \u0026#34;UpdatedAt\u0026#34;: \u0026#34;2019-10-06T09:01:41.9956973Z\u0026#34;, \u0026#34;Labels\u0026#34;: {}, \u0026#34;Spec\u0026#34;: { \u0026#34;ContainerSpec\u0026#34;: { \u0026#34;Image\u0026#34;: \u0026#34;uzimihsr/get-started:part2@sha256:ca9c71fd6d4195a2dd6e83f708383451b612910d64e7103a26f710b4428fbbc9\u0026#34;, \u0026#34;Labels\u0026#34;: { \u0026#34;com.docker.stack.namespace\u0026#34;: \u0026#34;getstartedlab\u0026#34; }, \u0026#34;Privileges\u0026#34;: { \u0026#34;CredentialSpec\u0026#34;: null, \u0026#34;SELinuxContext\u0026#34;: null }, \u0026#34;Isolation\u0026#34;: \u0026#34;default\u0026#34; }, \u0026#34;Resources\u0026#34;: { \u0026#34;Limits\u0026#34;: { \u0026#34;NanoCPUs\u0026#34;: 100000000, \u0026#34;MemoryBytes\u0026#34;: 52428800 } }, \u0026#34;RestartPolicy\u0026#34;: { \u0026#34;Condition\u0026#34;: \u0026#34;on-failure\u0026#34;, \u0026#34;MaxAttempts\u0026#34;: 0 }, \u0026#34;Placement\u0026#34;: { \u0026#34;Platforms\u0026#34;: [ { \u0026#34;Architecture\u0026#34;: \u0026#34;amd64\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;linux\u0026#34; } ] }, \u0026#34;Networks\u0026#34;: [ { \u0026#34;Target\u0026#34;: \u0026#34;uvihn12ykm2owgxebox1wxrb7\u0026#34;, \u0026#34;Aliases\u0026#34;: [ \u0026#34;web\u0026#34; ] } ], \u0026#34;ForceUpdate\u0026#34;: 0 }, \u0026#34;ServiceID\u0026#34;: \u0026#34;bggiqgkl98zvy8ayiqmwxyt79\u0026#34;, \u0026#34;Slot\u0026#34;: 1, \u0026#34;NodeID\u0026#34;: \u0026#34;hw3rcr1q9vlpp3qgfw959knwb\u0026#34;, \u0026#34;Status\u0026#34;: { \u0026#34;Timestamp\u0026#34;: \u0026#34;2019-10-06T09:01:41.9350846Z\u0026#34;, \u0026#34;State\u0026#34;: \u0026#34;running\u0026#34;, \u0026#34;Message\u0026#34;: \u0026#34;started\u0026#34;, \u0026#34;ContainerStatus\u0026#34;: { \u0026#34;ContainerID\u0026#34;: \u0026#34;374fec7c58a77a554ebdd5b079fcd72b36462275f99c730131f0a0b83744dfeb\u0026#34;, \u0026#34;PID\u0026#34;: 71366, \u0026#34;ExitCode\u0026#34;: 0 }, \u0026#34;PortStatus\u0026#34;: {} }, \u0026#34;DesiredState\u0026#34;: \u0026#34;running\u0026#34;, \u0026#34;NetworksAttachments\u0026#34;: [ { \u0026#34;Network\u0026#34;: { \u0026#34;ID\u0026#34;: \u0026#34;5w9oj87tachjift8phcql9ayo\u0026#34;, \u0026#34;Version\u0026#34;: { \u0026#34;Index\u0026#34;: 6 }, \u0026#34;CreatedAt\u0026#34;: \u0026#34;2019-10-06T08:55:54.1014898Z\u0026#34;, \u0026#34;UpdatedAt\u0026#34;: \u0026#34;2019-10-06T08:55:54.117508Z\u0026#34;, \u0026#34;Spec\u0026#34;: { \u0026#34;Name\u0026#34;: \u0026#34;ingress\u0026#34;, \u0026#34;Labels\u0026#34;: {}, \u0026#34;DriverConfiguration\u0026#34;: {}, \u0026#34;Ingress\u0026#34;: true, \u0026#34;IPAMOptions\u0026#34;: { \u0026#34;Driver\u0026#34;: {}, \u0026#34;Configs\u0026#34;: [ { \u0026#34;Subnet\u0026#34;: \u0026#34;10.255.0.0/16\u0026#34;, \u0026#34;Gateway\u0026#34;: \u0026#34;10.255.0.1\u0026#34; } ] }, \u0026#34;Scope\u0026#34;: \u0026#34;swarm\u0026#34; }, \u0026#34;DriverState\u0026#34;: { \u0026#34;Name\u0026#34;: \u0026#34;overlay\u0026#34;, \u0026#34;Options\u0026#34;: { \u0026#34;com.docker.network.driver.overlay.vxlanid_list\u0026#34;: \u0026#34;4096\u0026#34; } }, \u0026#34;IPAMOptions\u0026#34;: { \u0026#34;Driver\u0026#34;: { \u0026#34;Name\u0026#34;: \u0026#34;default\u0026#34; }, \u0026#34;Configs\u0026#34;: [ { \u0026#34;Subnet\u0026#34;: \u0026#34;10.255.0.0/16\u0026#34;, \u0026#34;Gateway\u0026#34;: \u0026#34;10.255.0.1\u0026#34; } ] } }, \u0026#34;Addresses\u0026#34;: [ \u0026#34;10.255.0.4/16\u0026#34; ] }, { \u0026#34;Network\u0026#34;: { \u0026#34;ID\u0026#34;: \u0026#34;uvihn12ykm2owgxebox1wxrb7\u0026#34;, \u0026#34;Version\u0026#34;: { \u0026#34;Index\u0026#34;: 12 }, \u0026#34;CreatedAt\u0026#34;: \u0026#34;2019-10-06T09:01:32.1749865Z\u0026#34;, \u0026#34;UpdatedAt\u0026#34;: \u0026#34;2019-10-06T09:01:32.1787669Z\u0026#34;, \u0026#34;Spec\u0026#34;: { \u0026#34;Name\u0026#34;: \u0026#34;getstartedlab_webnet\u0026#34;, \u0026#34;Labels\u0026#34;: { \u0026#34;com.docker.stack.namespace\u0026#34;: \u0026#34;getstartedlab\u0026#34; }, \u0026#34;DriverConfiguration\u0026#34;: { \u0026#34;Name\u0026#34;: \u0026#34;overlay\u0026#34; }, \u0026#34;Scope\u0026#34;: \u0026#34;swarm\u0026#34; }, \u0026#34;DriverState\u0026#34;: { \u0026#34;Name\u0026#34;: \u0026#34;overlay\u0026#34;, \u0026#34;Options\u0026#34;: { \u0026#34;com.docker.network.driver.overlay.vxlanid_list\u0026#34;: \u0026#34;4097\u0026#34; } }, \u0026#34;IPAMOptions\u0026#34;: { \u0026#34;Driver\u0026#34;: { \u0026#34;Name\u0026#34;: \u0026#34;default\u0026#34; }, \u0026#34;Configs\u0026#34;: [ { \u0026#34;Subnet\u0026#34;: \u0026#34;10.0.0.0/24\u0026#34;, \u0026#34;Gateway\u0026#34;: \u0026#34;10.0.0.1\u0026#34; } ] } }, \u0026#34;Addresses\u0026#34;: [ \u0026#34;10.0.0.3/24\u0026#34; ] } ] } ] アプリとswarmの停止 アプリ(スタック)を停止するにはdocker stack rmコマンドを使用する.\n$ docker stack rm getstartedlab Removing service getstartedlab_web Removing network getstartedlab_webnet また, swarmもdocker swarm leaveコマンドで停止する.\n--forceオプションで強制的に停止できる.\n$ docker swarm leave --force Node left the swarm. 感想(まとめ) 仕方ない部分があるが, swarmとスタックに関する説明が後回しなのはちょっとわかりづらいと思った.\nあと後半の説明が力尽きてる感があった.\n要点としては,\nコンテナを実用化するには負荷分散やスケーリングが必要になること それらの設定を管理するためにサービスを使うこと サービス(とネットワーク)はdocker-compose.ymlで定義できること アプリを起動する前にswarmクラスタを初期化すること swarmに関してはPart4で説明 アプリはスタックという単位で扱うこと スタックに関してはPart5で説明 レプリカ数を変化させることでアプリのスケーリングができること がわかれば十分だと思う.\n次はGet Started, Part 4: Swarmsを読む.\n","date":"2019-10-06T16:58:10+09:00","permalink":"/post/2019-10-06-docker-get-started-03/","title":"[Deprecated]Docker Get Startedを読む Part3"},{"content":"Dockerおさらい 前回のつづき\nDeprecated 公式のGet Startedが更新されてしまったので,\nこの記事の内容は古くなっている. 非推奨.\n読んだもの Get Started, Part 2: Containers\n主にコンテナをつくるためのDockerfileの書き方とか, イメージの取り扱いに関する内容.\n事前準備 このパートに入る前に以下の条件をクリアすること.\nバージョン1.13以降のDockerがインストール済みであること Part 1の内容を理解していること $ docker run hello-worldが正常に動くこと はじめに Dockerによるアプリ開発には大きく分けて3つの段階がある.\nスタック 一番上 全てのサービスの挙動を定義する Part 5で説明 サービス スタックとコンテナの中間 実際のコンテナの挙動を定義する Part 3で説明 コンテナ 一番下 このパートで説明 新しい開発環境 Pythonでのアプリ開発を例に通常のマシンとDockerでの開発を比較する.\n通常のマシンの場合: Pythonランタイムのインストールが必要 アプリの動作/開発環境のセットアップが必要 (超意訳)アプリが動作するための依存関係とかを全部クリアする必要があるので, マシンが他の用途に使いづらくなる Dockerの場合: Pythonランタイムが動くイメージがすでにあるのでインストールが不要 コードや依存するパッケージなどを全て1つのイメージにまとめることができる そのイメージはDockerfileを使って定義できる Dockerfileによるコンテナの定義 Dockerfileはコンテナの中身を定義する.\nコンテナ内のネットワークインターフェースとかディスクドライブは,\nコンテナを動作させるシステムの環境からは切り離されて仮想化されている.\nしたがって, 開発者はコンテナのポートを外部に開放する設定と,\nアプリに必要なファイルのコンテナ内へのコピーに気を配るだけで良い.\nたったそれだけで, どの環境でもDockerさえあれば同じように動作するコンテナを作ることができる.\nDockerfile 実際にDockerfileを書いてみる.\n適当なディレクトリで, Dockerfileという名前のファイルを以下の内容で作成する.\n$ mkdir workspace $ cd workspace $ vim Dockerfile Dockerfile # FROM : 親イメージの指定を行う. # pythonの公式イメージをこれからつくるイメージの親イメージとして使用する FROM python:2.7-slim # WORKDIR : イメージ内での作業ディレクトリを指定する. # このイメージ内での作業ディレクトリを/appにする # /appがない場合は自動で生成される WORKDIR /app # COPY : ローカルの環境からイメージにファイルをコピーする. # このDockerfileを開いている現在のディレクトリ(workspace)の内容をイメージ内の/appにコピーする COPY . /app # RUN : イメージの中でコマンドを実行する. # pip(pythonのパッケージマネージャ)を使用して, requirements.txt(コピーしてきたもの)に記述されている必要なパッケージを全てこのイメージにインストールする # このDockerfileを編集している今のシステムにはインストールされない RUN pip install --trusted-host pypi.python.org -r requirements.txt # EXPOSE : コンテナのポートを外部に開放する. # このコンテナの80番ポートを外部からアクセス可能にする EXPOSE 80 # ENV : コンテナ内での環境変数を定義する # 環境変数NAMEにworldを定義する ENV NAME World # CMD : コンテナが起動した際に実行するコマンドを定義する. # コンテナが起動した際に`$ python app.py`を実行する CMD [\u0026#34;python\u0026#34;, \u0026#34;app.py\u0026#34;] ざっくり説明するとこのDockerfileは\npythonが動かせるイメージを持ってくる 必要なファイルをローカルからコピーする アプリに必要なパッケージとかの設定をする コンテナのポートを開放する アプリの起動コマンドを設定する という一連の操作を定義している.\nrequirements.txtとapp.pyはこのあとイメージをビルドする前に作成する.\n今回作るアプリ Dockerfile内で使用するrequirements.txtとapp.pyを作成する.\n$ pwd /path/to/workspace $ vim requirements.txt $ vim app.py requirements.txt Flask Redis Flask : pythonでwebアプリを作るためのフレームワーク\nRedis : データベース(Redis)を扱うためのパッケージ\napp.py from flask import Flask from redis import Redis, RedisError import os import socket # Connect to Redis redis = Redis(host=\u0026#34;redis\u0026#34;, db=0, socket_connect_timeout=2, socket_timeout=2) app = Flask(__name__) @app.route(\u0026#34;/\u0026#34;) def hello(): try: visits = redis.incr(\u0026#34;counter\u0026#34;) except RedisError: visits = \u0026#34;\u0026lt;i\u0026gt;cannot connect to Redis, counter disabled\u0026lt;/i\u0026gt;\u0026#34; html = \u0026#34;\u0026lt;h3\u0026gt;Hello {name}!\u0026lt;/h3\u0026gt;\u0026#34; \\ \u0026#34;\u0026lt;b\u0026gt;Hostname:\u0026lt;/b\u0026gt; {hostname}\u0026lt;br/\u0026gt;\u0026#34; \\ \u0026#34;\u0026lt;b\u0026gt;Visits:\u0026lt;/b\u0026gt; {visits}\u0026#34; return html.format(name=os.getenv(\u0026#34;NAME\u0026#34;, \u0026#34;world\u0026#34;), hostname=socket.gethostname(), visits=visits) if __name__ == \u0026#34;__main__\u0026#34;: app.run(host=\u0026#39;0.0.0.0\u0026#39;, port=80) requirements.txtは今回作成するFlaskアプリに必要なパッケージを定義し,\napp.pyはHTTPアクセスに対して変数htmlで定義された内容(環境変数NAME, 接続してきたホスト名, DBで保存しているカウント数)を表示する.\nこれで必要なファイルの準備は完了.\nしかしRedisが実際にイメージの中でまだ動いていないため,\nこのまま起動してもエラーメッセージが表示されることに注意.\n(resuirements.txtで入れたのはあくまでpythonからRedisを使うためのパッケージ)\n以上でアプリを動かす準備ができた.\n本来このFlaskアプリを動作させるためにはPythonやパッケージ(Flask, Redis)をシステムにインストールする必要があるが,\n今回はそれらがすべてイメージ内で行われるためその必要がない.\nまた, Dockerで使用するイメージはそれ単体が存在するだけで使用できるので,\nイメージをシステムにインストールする必要もない. 超便利!\n(実際にDockerを使わずに何もない環境からこのアプリを作るのはちょっと面倒)\nアプリのビルド アプリに必要なものは全て準備できたので, さっそくビルドする.\nまずは現在のディレクトリにDockerfile, app.py, requirements.txtがあることを確認する.\n$ pwd /path/to/workspace $ ls Dockerfile app.py requirements.txt ビルド用のコマンドdocker buildを使用してイメージのビルドを行う.\n-t repository:tagのオプションでリポジトリ名:タグ(≒イメージ名)の指定ができる.\n(tagを指定しない場合は自動的にlatestが付与される. 今はそんなに重要じゃない.)\n最後の.は現在のディレクトリにあるDockerfileを使用することを示す.\n今回はfriendlyhelloというリポジトリ名でイメージをビルドする.\n$ docker build -t friendlyhello . 余談だが, ビルド時のログを見るとDockerfileの内容を1行ずつ実行していることがわかる.\nビルド時のログ $ docker build -t friendlyhello . Sending build context to Docker daemon 5.12kB Step 1/7 : FROM python:2.7-slim 2.7-slim: Pulling from library/python b8f262c62ec6: Pull complete 8cbb51e0b077: Pull complete 82627a456962: Pull complete 33f3f5c560fe: Pull complete Digest: sha256:68bb099b780cf7aa60df3af68d573dc420907acfa54cbb2a53ade8886d965272 Status: Downloaded newer image for python:2.7-slim ---\u0026gt; f462855313cd Step 2/7 : WORKDIR /app ---\u0026gt; Running in 4d73545dac95 Removing intermediate container 4d73545dac95 ---\u0026gt; 9cd55a4d5845 Step 3/7 : COPY . /app ---\u0026gt; 689b85f40a7f Step 4/7 : RUN pip install --trusted-host pypi.python.org -r requirements.txt ---\u0026gt; Running in e8a28b64a049 DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won\u0026#39;t be maintained after that date. A future version of pip will drop support for Python 2.7. More details about Python 2 support in pip, can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support Collecting Flask (from -r requirements.txt (line 1)) Downloading https://files.pythonhosted.org/packages/9b/93/628509b8d5dc749656a9641f4caf13540e2cdec85276964ff8f43bbb1d3b/Flask-1.1.1-py2.py3-none-any.whl (94kB) Collecting Redis (from -r requirements.txt (line 2)) Downloading https://files.pythonhosted.org/packages/bd/64/b1e90af9bf0c7f6ef55e46b81ab527b33b785824d65300bb65636534b530/redis-3.3.8-py2.py3-none-any.whl (66kB) Collecting click\u0026gt;=5.1 (from Flask-\u0026gt;-r requirements.txt (line 1)) Downloading https://files.pythonhosted.org/packages/fa/37/45185cb5abbc30d7257104c434fe0b07e5a195a6847506c074527aa599ec/Click-7.0-py2.py3-none-any.whl (81kB) Collecting Werkzeug\u0026gt;=0.15 (from Flask-\u0026gt;-r requirements.txt (line 1)) Downloading https://files.pythonhosted.org/packages/ce/42/3aeda98f96e85fd26180534d36570e4d18108d62ae36f87694b476b83d6f/Werkzeug-0.16.0-py2.py3-none-any.whl (327kB) Collecting itsdangerous\u0026gt;=0.24 (from Flask-\u0026gt;-r requirements.txt (line 1)) Downloading https://files.pythonhosted.org/packages/76/ae/44b03b253d6fade317f32c24d100b3b35c2239807046a4c953c7b89fa49e/itsdangerous-1.1.0-py2.py3-none-any.whl Collecting Jinja2\u0026gt;=2.10.1 (from Flask-\u0026gt;-r requirements.txt (line 1)) Downloading https://files.pythonhosted.org/packages/65/e0/eb35e762802015cab1ccee04e8a277b03f1d8e53da3ec3106882ec42558b/Jinja2-2.10.3-py2.py3-none-any.whl (125kB) Collecting MarkupSafe\u0026gt;=0.23 (from Jinja2\u0026gt;=2.10.1-\u0026gt;Flask-\u0026gt;-r requirements.txt (line 1)) Downloading https://files.pythonhosted.org/packages/fb/40/f3adb7cf24a8012813c5edb20329eb22d5d8e2a0ecf73d21d6b85865da11/MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl Installing collected packages: click, Werkzeug, itsdangerous, MarkupSafe, Jinja2, Flask, Redis Successfully installed Flask-1.1.1 Jinja2-2.10.3 MarkupSafe-1.1.1 Redis-3.3.8 Werkzeug-0.16.0 click-7.0 itsdangerous-1.1.0 Removing intermediate container e8a28b64a049 ---\u0026gt; 5aeaca8be74e Step 5/7 : EXPOSE 80 ---\u0026gt; Running in 7ee28830810e Removing intermediate container 7ee28830810e ---\u0026gt; a8c8153b4bd3 Step 6/7 : ENV NAME World ---\u0026gt; Running in 98d95719d709 Removing intermediate container 98d95719d709 ---\u0026gt; cfd1c0282f2e Step 7/7 : CMD [\u0026#34;python\u0026#34;, \u0026#34;app.py\u0026#34;] ---\u0026gt; Running in 5e9ef2b09c2a Removing intermediate container 5e9ef2b09c2a ---\u0026gt; c807461f0dca Successfully built c807461f0dca Successfully tagged friendlyhello:latest ビルドしたイメージを確認する.\ndocker image lsコマンドでローカルマシンにあるイメージの一覧が取得できる.\n$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE friendlyhello latest c807461f0dca 9 minutes ago 148MB アプリの実行 いよいよ作成したイメージからコンテナを起動し, アプリを実行する.\ndocker runコマンドで使用するイメージを指定するとコンテナが立ち上がる.\n-p localport:containerportオプションでコンテナを起動するローカルマシンのポート(localport)をコンテナのポート(containerport)に割り当てることができる.\n今回はローカルマシンの4000番ポートを起動するコンテナの80番ポートに割り当てる.\n$ docker run -p 4000:80 friendlyhello * Serving Flask app \u0026#34;app\u0026#34; (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: off * Running on http://0.0.0.0:80/ (Press CTRL+C to quit) Flaskのログが表示され, http://0.0.0.0:80/にアクセスするよう促されるが,\nこれはあくまでコンテナ内でのメッセージなので,\nコンテナの80番ポートに割り当てられているローカルマシンの4000番ポート(http://localhost:4000) をブラウザで開く.\nアプリが動作して, Hello, World!とホスト名が表示される.\n前述の通り, イメージ内にRedisがないのでその旨を示すエラーメッセージも表示されている.\n一旦Ctrl+Cでコンテナを停止し, 今度は-dオプションをつけてバックグラウンドでコンテナを起動する.\nこの起動方法をデタッチドモードと言う.\n$ docker run -d -p 4000:80 friendlyhello 先程はブラウザで開いたので, 今度はcurlコマンドで動作確認する.\n$ curl http://localhost:4000 \u0026lt;h3\u0026gt;Hello World!\u0026lt;/h3\u0026gt;\u0026lt;b\u0026gt;Hostname:\u0026lt;/b\u0026gt; xxxxxxxxxxxx\u0026lt;br/\u0026gt;\u0026lt;b\u0026gt;Visits:\u0026lt;/b\u0026gt; \u0026lt;i\u0026gt;cannot connect to Redis, counter disabled\u0026lt;/i\u0026gt; ブラウザのときと同じHTTPレスポンスが返ってくることがわかる.\nまた, 起動中のコンテナ一覧をdocker container lsコマンドで確認できる.\n$ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES cc47aa120f15 friendlyhello \u0026#34;python app.py\u0026#34; 58 seconds ago Up 57 seconds 0.0.0.0:4000-\u0026gt;80/tcp zen_liskov 今度は起動中のコンテナを停止してみる.\nコンテナの停止にはdocker container stopコマンドを使用する.\n停止するコンテナはCONTAINER IDで指定する.\n$ docker container stop cc47aa120f15 イメージをシェアする ビルドしたイメージを別の環境でも動かすためにはレジストリにアップロードする必要がある.\nレジストリとはリポジトリが集まる場所で, リポジトリとはイメージの集まりのことである.\nレジストリの1つのアカウントは複数のリポジトリを作ることができるので,\n感覚的にはレジストリがGitHub, リポジトリがGitHubリポジトリに似ている.\nデフォルトではDocker Hubがレジストリとして使用される.\nDocker IDでログインする 事前にhub.docker.comでアカウントを作成しておく.\nローカルマシンからDocker Hubにログインするにはdocker loginコマンドを使用する.\n$ docker login イメージにタグをつける レジストリ上でイメージはusername/repository:tagの形式で識別される(usernameはレジストリID).\nDockerイメージにはタグで意味のあるバージョン名または番号を付与する必要があるので,\nイメージをアップロードする前にはタグをつけ直す必要がある.\nタグの付与にはdocker tagコマンドを使用する.\n今回は作成したfriendlyhelloイメージにuzimihsr/get-started:part2という名前をつける.\n$ docker tag friendlyhello uzimihsr/get-started:part2 イメージ一覧を表示すると, 先程まで使用していたfriendlyhelloと同じイメージIDのuzimihsr/get-startedが作成されている.\n$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE friendlyhello latest c807461f0dca About an hour ago 148MB uzimihsr/get-started part2 c807461f0dca About an hour ago 148MB イメージを公開する タグ付けしたイメージはdocker pushコマンドでアップロードできる.\n先程タグを付け直したuzimihsr/get-startedをアップロードする.\n$ docker push uzimihsr/get-started:part2 実際にアップロードしたイメージはここ.\nhttps://hub.docker.com/r/uzimihsr/get-started/tags\nリモートリポジトリから入手したイメージを実行する Docker Hubにイメージの公開ができたので, 試しに公開したイメージを使ってコンテナを起動してみる.\n確実にリモートリポジトリから取得したイメージを使用するため, 現在手元にあるイメージとコンテナを削除する.\nコンテナの削除にはdocker container rmコマンドを使用する.\n削除するコンテナのIDを指定するか, $(docker container ls -a -q)を指定すると全てのコンテナを削除してくれる.\n同様にイメージの削除にはdocker image rmコマンドを使用する.\n今回はコンテナを全削除し, 作成したイメージを削除する.\n$ docker container rm $(docker container ls -a -q) $ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES $ docker image rm -f c807461f0dca $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE Docker Hubにあるイメージを指定してコンテナを起動させる.\nPart1でhello-worldを起動したときと同様に, イメージが手元に無いので自動でダウンロードしてくる.\n$ docker run -p 4000:80 uzimihsr/get-started:part2 Unable to find image \u0026#39;uzimihsr/get-started:part2\u0026#39; locally part2: Pulling from uzimihsr/get-started b8f262c62ec6: Already exists 8cbb51e0b077: Already exists 82627a456962: Already exists 33f3f5c560fe: Already exists c94901432fd6: Pull complete 15e44dce546a: Pull complete 7f08569cb4d3: Pull complete Digest: sha256:ca9c71fd6d4195a2dd6e83f708383451b612910d64e7103a26f710b4428fbbc9 Status: Downloaded newer image for uzimihsr/get-started:part2 * Serving Flask app \u0026#34;app\u0026#34; (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: off * Running on http://0.0.0.0:80/ (Press CTRL+C to quit) 再度 http://localhost:4000/ にアクセスすると今までと同様にアプリが起動していることが確認できる.\n感想 Dockerfileのあたりはイメージの根幹を成す部分なのでかなり内容がもりだくさんだった.\n要点としては,\nDockerfileを使ってイメージ(コンテナ)を定義できること 親となるイメージにいろいろ操作を加えて自分のアプリ用の新しいイメージが作れること イメージ内の環境は隔離されていて, ローカルマシンのシステムには影響がないこと イメージはタグで管理されること ビルドしたイメージはDocker Hubを使って別環境からも参照できること がわかればこのパートは十分だと思う.\n次はGet Started, Part 3: Servicesを読みたい.\n(追記)読んだ\n","date":"2019-10-05T12:47:56+09:00","permalink":"/post/2019-10-05-docker-get-started-02/","title":"[Deprecated]Docker Get Startedを読む Part2"},{"content":"Dockerおさらい DockerについてKatacodaで一通り勉強したので, 復習も兼ねて公式ドキュメントを読んでみる.\nDeprecated 公式のGet Startedが更新されてしまったので,\nこの記事の内容は古くなっている. 非推奨.\n読んだもの Get Started, Part 1: Orientation and setup\nこの記事の内容を自分の主観バリバリで超意訳しながらまとめていく.\nDockerのコンセプト Dockerとはなんぞや? コンテナでアプリを開発, デプロイ, 実行するためのプラットフォーム. コンテナ自体は前からある技術だけど, コンテナを使ってアプリを簡単にデプロイできる点が新しい. コンテナ化すると良いこと どんなに複雑なアプリでもコンテナ化が可能 複数のコンテナで同じカーネルを活用するため軽量 臨機応変にアプリの更新が可能 ローカルでも, クラウド上でも, どこでも利用できる コンテナの複製が簡単なのでスケーラビリティが高い コンテナ同士を柔軟に組み合わせて利用できる イメージとコンテナ コンテナってどう作るの? イメージを実行することで起動する じゃあイメージってなに? アプリを動かすために必要なものが全部入った実行可能なパッケージ イメージが実行状態になるとコンテナになる コンテナと仮想マシンのちがい コンテナ アプリの実行に必要な環境をつくる(OSは1つ) コンテナ単体で実行できる 実行時に必要な分のリソース(メモリ)しか食わない 一言で言えば軽いが目的の用途(アプリの実行)にしか使えない 仮想マシン(VM) 本当のマシンとほぼ同じ環境を作る(それぞれにOSがある) VMを管理するハイパーバイザを通さないとアクセスできない VMを維持している間はそれらを管理するマシンのリソースを喰っている 一言で言えば重いが用途が多彩 Dockerの環境構築 自分の場合はDocker Desktop for Macをインストールした.\n簡単なので手順は割愛.\ndocker-composeとKubernetesも入っているので便利.\nDockerのバージョン確認 さっそくバージョン確認用のコマンドを打ってみる.\n# 普通のアプリと同様に--versionまたは-vオプションで確認可能 $ docker --version Docker version 19.03.2, build 6a30dfc さらに詳細な情報を表示する # 詳細情報を表示 # イメージ, コンテナの数とかが表示される $ docker info Client: Debug Mode: false Server: Containers: 70 Running: 32 Paused: 0 Stopped: 38 Images: 74 Server Version: 19.03.2 Storage Driver: overlay2 Backing Filesystem: extfs Supports d_type: true Native Overlay Diff: true Logging Driver: json-file Cgroup Driver: cgroupfs Plugins: Volume: local Network: bridge host ipvlan macvlan null overlay Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog Swarm: inactive Runtimes: runc Default Runtime: runc Init Binary: docker-init containerd version: 894b81a4b802e4eb2a91d1ce216b8817763c29fb runc version: 425e105d5a03fabd737a126ad93d62a9eeede87f init version: fec3683 Security Options: seccomp Profile: default Kernel Version: 4.9.184-linuxkit Operating System: Docker Desktop OSType: linux Architecture: x86_64 CPUs: 4 Total Memory: 1.952GiB Name: docker-desktop ID: K77N:CV6M:AWUD:E3CZ:B44M:DJSX:4GLO:SNXJ:LXBZ:HUML:A46J:SYHZ Docker Root Dir: /var/lib/docker Debug Mode: true File Descriptors: 128 Goroutines: 122 System Time: 2019-10-03T14:18:37.8900535Z EventsListeners: 2 HTTP Proxy: gateway.docker.internal:3128 HTTPS Proxy: gateway.docker.internal:3129 Registry: https://index.docker.io/v1/ Labels: Experimental: false Insecure Registries: 127.0.0.0/8 Live Restore Enabled: false Product License: Community Engine Dockerの動作確認 DockerでHello, World!する.\n# Hello, World!を表示させるイメージを実行する # hello-worldという名前のイメージはまだローカルにないので, Docker Hubから自動で取ってくる. $ docker run hello-world Unable to find image \u0026#39;hello-world:latest\u0026#39; locally latest: Pulling from library/hello-world Digest: sha256:b8ba256769a0ac28dd126d584e0a2011cd2877f3f76e093a7ae560f2a5301c00 Status: Downloaded newer image for hello-world:latest Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the \u0026#34;hello-world\u0026#34; image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/ Docker Hub : いろんなイメージが共有されてるクラウドサービス, イメージ専門のGitHubみたいなもん\n# 手元にあるイメージを一覧表示 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE hello-world latest fce289e99eb9 9 months ago 1.84kB REPOSITORY : イメージの名前(タグがついていないもの)\nTAG : イメージに付くタグ, バージョンとかを表す\nIMAGE ID : イメージに振られるID\nCREATED : イメージがいつ作成されたか\nSIZE : イメージのサイズ\n感想 イメージより先にいきなりコンテナの話から始まるのでちょっととっつきづらかった.\nコンテナの特徴とかVMとの違いとか自分の中であやふやだったのでなんとなく整理できてよかった.\n次はGet Started, Part 2: Containersを読みたい.\n(追記)読んだ:Part2\n","date":"2019-10-03T23:54:16+09:00","permalink":"/post/2019-10-03-docker-get-started-01/","title":"[Deprecated]Docker Get Startedを読む Part1"},{"content":"1人暮らしで猫を養いはじめるのにかかったお金 最近友人に質問されたので, そとちゃんを迎えるときに買い揃えたものと金額をまとめた.\nもくじ そとちゃん 買ったもの そとちゃん そとちゃん 3歳 女の子(不妊手術済)\n元保護猫\nかわいい\nあそぶのが好き\n買ったもの 基本的にトイレ, 爪とぎ, キャリーバッグは必須だと思う.\nモノにもよるけどその3点だけなら1~2万円,\nさらに便利なグッズを揃えると追加で3万円くらいかかる.\n品目 だいたいの値段 ワクチン代 5,000円 システムトイレ 3,500円 猫砂 900円 トイレシート 1,300円 爪とぎベッド 1,800円 キャリーバッグ 8,000円 ごはん皿, 水飲み皿 2,000円 電子はかり 1,000円 キャットタワー 10,000円 自動給餌器 18,000円 浄水器 5,000円 合計 56,500円 ワクチン代\n必須.\nそとちゃんは保護猫団体の方でワクチン接種してもらっていたので,\nその分はこちらで負担した.\nたぶんどこの団体でも同じくらい請求されると思う.\nシステムトイレ, 猫砂, トイレシート\nアイリスオーヤマ 上から猫トイレ システムタイプ\nアイリスオーヤマ システムトイレ用 におわない消臭サンド\nアイリスオーヤマ システムトイレ用 1週間におわない脱臭シート\n必須.\n猫のおしっこはかなり臭い.\nこのタイプのトイレならにおいが気になりにくいし, 砂も部屋に飛び散らないのでとても良い.\nあと写真は撮れなかったけど入る前におててをトイレのフチに揃えて中を覗く仕草がとてもかわいい.\nそとちゃんは砂かけが下手なのでうんちのにおいは漏れてくる\n爪とぎベッド\n猫壱 バリバリボウル\n必須.\n猫は想像以上に爪とぎをする.\n爪とぎがないと平気で壁紙をガリガリする.\nこれなら爪とぎとお昼寝どっちにも使えるし,\nへたってきたら中身の爪とぎだけ交換できるのでとても便利.\nそとちゃんはこれがあってもたまに壁で爪とぎする\nキャリーバッグ\nTOUGHCAT キャリーバッグ\n必須.\n病院につれていくときに必要になる.\nうちの場合は動物病院まで徒歩10分なのでリュックタイプの小さいバッグだけど,\n長距離移動するときはケージみたいなもっと大きいのが必要になると思う.\nごはん皿, 水飲み皿, 電子はかり\n猫壱 脚付フードボウル\n猫壱 脚付ウォーターボウル\nタニタ はかり KF-100 WH\nあると便利.\nごはんと水は普通のお茶碗であげても良いんだけど,\n少し高さがついている専用の皿のほうが食べやすそう.\nはかりはドライフードの重さを計ってカロリーを計算するのに使う.\n完全室内飼いの猫は太りやすいので\u0026hellip;\nキャットタワー\nFEANDREA キャットタワー NPCT99W\nあると便利.\nベランダの近くに設置しているので, そとちゃんはよくタワーの上から外の景色を眺めている.\n運動不足の解消にもなるので, あって困ることは無いと思う.\n問題は1人暮らしの部屋(6.5畳)だとかなり場所を取られること.\n早く広い家に引っ越したい.\n自動給餌器\nうちのこエレクトリック カリカリマシーンSP\nあると便利.\n自分が昼間は家を空けているので,\nこれを使ってカリカリ(ドライフード)を決まった時間に小分けにして出すようにしている.\n忙しい朝もごはんを用意する必要が無いので本当に助かる.\nWebカメラとマイク, スピーカーがついていてスマホのアプリからそとちゃんの様子が見られたり,\nこちらから声をかけたりできるので外出中も安心できる.\nなお, そとちゃんはごはんの時間以外カメラには映らない模様\u0026hellip;\n浄水器\nジェックス ピュアクリスタル セラミックス\nあると便利.\n毎朝飲み水を換える作業が面倒だったので購入.\n中に軟水化フィルターが入っていていつでもきれいな水が飲めるようになっている.\n水が流れると何故か猫はよく飲むみたいで, そとちゃんも本当によく飲む.\nまとめ そとちゃんを迎えるときに買ったものをまとめてみた.\n次はそとちゃんのごはんについて書きたい.\n","date":"2019-09-23T15:05:07+09:00","image":"/post/2019-09-23-sotochan-money/2019-09-23-sotochan-meow.jpg","permalink":"/post/2019-09-23-sotochan-money/","title":"そとちゃんと初期費用"},{"content":"AtomってIDEじゃなくね? 最近Goに触りだしたので, 普段使ってるエディタ(Atom)をGo向けにセットアップしてみた.\nやったこと AtomをGoのIDEっぽくする.\nIDEにするって言い切ると詳しい人に怒られそう.\nつかうもの MacBook Pro (Retina, 15-inch, Mid 2015) macOS Mojave 10.14 Atom 1.40.1 インストール手順 Go go version go1.13 darwin/amd64 インストール手順 手順 atom-ide-uiのインストール scriptのインストール go-plusのインストール go-debugのインストール go-signature-statusbarのインストール atom-ide-uiのインストール atom-ide-ui\nAtomをIDEっぽくしてくれるパッケージ.\nGoの場合はgo-plusとの併用が必要になってくる.\n$ apm install atom-ide-ui scriptのインストール script\nAtom上でコードを実行してくれるパッケージ.\n⌘+iで現在開いているコードを実行できる.\n.goファイルの場合はgo runするのと同じことをしてくれる.\n他の言語で実行オプションを細かく設定したい場合などは$HOME/.atom/packages/script/lib/grammarsにある設定ファイルを編集すれば良い.\n$ apm install script go-plusのインストール go-plus\n本命. AtomでGoを書くときのサポート(整形, 補完, docの簡易表示, 関数定義ファイルへのジャンプ)をだいたいやってくれる.\nGoのパッケージをいくつか必要とするので, それらもインストールする.\n$ go get -u golang.org/x/tools/cmd/goimports $ go get -u golang.org/x/tools/cmd/gorename $ go get -u github.com/sqs/goreturns $ go get -u github.com/mdempsky/gocode $ go get -u github.com/alecthomas/gometalinter $ go get -u github.com/mgechev/revive $ go get -u github.com/golangci/golangci-lint/cmd/golangci-lint $ go get -u github.com/zmb3/gogetdoc $ go get -u github.com/zmb3/goaddimport $ go get -u github.com/rogpeppe/godef $ go get -u golang.org/x/tools/cmd/guru $ go get -u github.com/fatih/gomodifytags $ go get -u github.com/tpng/gopkgs $ go get -u github.com/ramya-rao-a/go-outline $ apm install go-plus go-debugのインストール go-debug\ngo-plusで使用する. Goのデバッガ.\n$ go get -u github.com/go-delve/delve/cmd/dlv $ apm install go-debug go-signature-statusbarのインストール go-signature-statusbar go-plusで使用する. 画面下のステータスバーにいい感じの情報を載せてくれる.\n$ apm install go-signature-statusbar つかいかたの確認 現状使いこなせてるのはこの程度.\nキーバインド 機能 ⌘+s 保存時に自動でgoimportsでコード整形が行われる. ⌘+i 現在表示しているコードを実行する. ⌘+click godefで関数定義へジャンプする. 動的にlintしてくれないのが残念だけど, そもそもGoのコード整形自体が強力なのであまり気にならなさそう.\n","date":"2019-09-07T11:47:25+09:00","permalink":"/post/2019-09-07-golang-atom/","title":"Go向けにAtomの設定をした"},{"content":"なんか作りたい A Tour of Goを一通りなぞったので, なんか作りたくなった.\nやったこと Goの練習として, コマンドラインで動くかんたんなサイコロを作ってみた.\n動作のイメージ :\n$ dice # 1~6の範囲でランダムに生成された整数を1つ出力 3 $ dice -f 10 # -fオプションでサイコロの最大値を変更 9 $ dice -d 2 # -dオプションで振るサイコロの数を変更 5 4 $ dice -f 10 -d 2 # -fオプションと-dオプションの併用も可能 6 9 つかうもの MacBook Pro (Retina, 15-inch, Mid 2015) macOS Mojave 10.14 Go go version go1.12.9 darwin/amd64 インストール手順 手順 dice.goの作成 dice.goをインストール dice.goの作成 とりあえず標準パッケージを使って作ってみた.\n$ mkdir -p $GOPATH/src/github.com/uzimihsr/dice $ cd $GOPATH/src/github.com/uzimihsr/dice $ vim dice.go dice.go\npackage main import ( \u0026#34;flag\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;math/rand\u0026#34; \u0026#34;time\u0026#34; ) func main() { // コマンドラインオプションで与える値の変数定義 var ( faces uint dices uint ) // コマンドラインオプションの設定 flag.UintVar(\u0026amp;faces, \u0026#34;f\u0026#34;, 6, \u0026#34;The number of dice faces\u0026#34;) flag.UintVar(\u0026amp;dices, \u0026#34;d\u0026#34;, 1, \u0026#34;The number of dices to throw\u0026#34;) flag.Parse() // サイコロを振り, 出目を出力 rand.Seed(time.Now().UnixNano()) for i := 0; i \u0026lt; int(dices); i++ { fmt.Printf(\u0026#34;%d \u0026#34;, (rand.Intn(int(faces)) + 1)) } fmt.Println() } Goでコマンドラインツールを作る場合にはurfave/cliとかcobraが良いらしいんだけど,\n今回はそこまで高機能なものはつくらないので標準パッケージflagを使ってオプションの処理をした.\nサイコロの処理(乱数の生成)にはこちらも標準のmath/randパッケージを使った.\nそのまま使うと乱数のSeedが1で固定され, 毎回同じ値が生成されてしまうのでtimeパッケージで呼び出したUnix時間を乱数Seedとして使うようにした.\ndice.goをインストール go installを使ってdice.goの実行ファイルを$GOPATH/binにインストールする.\n$ cd $GOPATH/src/github.com/uzimihsr/dice $ go install $ which dice {$GOPATH}/bin/dice $ dice -f 12 -d 6 1 6 8 9 11 3 いい感じ.\nつくったやつはここ.\nおまけ ","date":"2019-09-04T22:15:18+09:00","image":"/post/2019-09-04-golang-dice/2019-09-04-sotochan-omake.jpg","permalink":"/post/2019-09-04-golang-dice/","title":"Goでコマンドラインサイコロを作った"},{"content":"需要があるかはわからない ねこ記事でアクセス数稼ぎをするのはいいが, そのアクセス数を数える方法がなかったのでやってみた.\nなにをしたか Google AnalyticsをGitHub Pages上に公開している自分のブログ(Hugoで生成)に埋め込み, トラフィックを見られるようにした\n必要なもの Googleアカウント どうやったか Google Analyticsの利用登録 トラッキングコードをHugoに埋め込む Google Analyticsの利用登録 Google Analyticsにアクセスし, Googleアナリティクスの利用を開始 -\u0026gt; 登録と進む.\nアカウントの設定では自分のGoogleアカウント名を入力し, アカウントのデータ共有設定で必要なものにチェックをつける. スクショ失敗したので画像が無いが, 自分は全部チェックをつけた.\n測定の対象を指定します。ではウェブを指定する.\nプロパティの設定でウェブサイトの名前, URLを入力する.\n最後に利用規約に同意したらGoogle Analyticsのダッシュボードが開く.\nここで表示されるトラッキングコード(UA-123456789-1)を控えておく.\nトラッキングコードをHugoに埋め込む Google Analyticsの準備ができたら, config.tomlに控えたトラッキングコードを記述する. ここに記述しておくと, Hugoで静的ページをビルドした際にトラッキング用のタグをすべてのhtmlに書き込んでくれるので便利.\nconfig.toml\nbaseurl = \u0026#34;https://uzimihsr.github.io\u0026#34; DefaultContentLanguage = \u0026#34;en\u0026#34; title = \u0026#34;meow.md\u0026#34; theme = \u0026#34;beautifulhugo\u0026#34; metaDataFormat = \u0026#34;yaml\u0026#34; pygmentsStyle = \u0026#34;trac\u0026#34; pygmentsUseClasses = true pygmentsCodeFences = true pygmentsCodefencesGuessSyntax = true author = false googleAnalytics = \u0026#34;UA-123456789-1\u0026#34; # この行を追記 ...(以下省略) 静的ページをビルドする. 自分の場合はビルド用スクリプトdeploy.shを作成しているのでこれをそのまま使う.\n$ ./deploy.sh \u0026#34;Google Analytics\u0026#34; もう一度ダッシュボードを更新すると, 設定したブログへのトラフィックが確認できる.\nまだぜんぜんトラフィックがない. 悲しい\u0026hellip;\nおまけ ","date":"2019-08-26T22:15:39+09:00","image":"/post/2019-08-26-google-analytics/2019-08-26-sotochan-omake.jpg","permalink":"/post/2019-08-26-google-analytics/","title":"GitHub Pages+HugoでつくったブログにGoogle Analyticsを埋め込む"},{"content":"go version は使えるけども pythonのsys.versionみたいにGo(golang)でもコード中でバージョンを確認する方法があったのでメモ.\nなにができるようになるか Goのソースコード内でバージョン情報をstringで扱えるようになる.\nやりかた runtime.Version()を使えば良い. かんたん.\n試しにgoenvでGoのバージョンを変えながらコマンドライン上でバージョンを確認するgo versionとコード中でバージョンを確認するcheck-version.goを交互に動かしてみる.\ncheck-version.go\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;runtime\u0026#34; ) func main() { fmt.Println(\u0026#34;Go version :\u0026#34;, runtime.Version()) } $ goenv versions 1.11.13 * 1.12.9 (set by /Users/username/.goenv/version) $ goenv global 1.12.9 # すでに設定されてるので意味無し $ go version go version go1.12.9 darwin/amd64 $ go run check-version.go Go version : go1.12.9 $ goenv global 1.11.13 $ go version go version go1.11.13 darwin/amd64 $ go run check-version.go Go version : go1.11.13 できた. サンプルコードで実行環境吐き出させるときとかにつかいたい.\nおまけ ","date":"2019-08-25T22:13:29+09:00","image":"/post/2019-08-25-golang-check-version/2019-08-25-sotochan-omake.jpg","permalink":"/post/2019-08-25-golang-check-version/","title":"コード中でGoのバージョンを確認する"},{"content":"そとちゃんは保護猫 そとちゃんは元野良猫(保護猫). 保護猫活動をしている方から譲ってもらってうちにきた.\nその方に聞いた話などから, わかっている範囲でこれまでのそとちゃんについて書いていく.\nもくじ そとちゃん, 生まれる そとちゃん, 保護される そとちゃんと俺, 出会う そとちゃん, うちに来る 今のそとちゃん そとちゃん, 生まれる そとちゃんはおそらく2016~2017年ごろに北関東のあたりで生まれた. 柄はキジトラ白なので, おそらく雑種.\nかなり人馴れしているのでもともとは人に飼われてたか, 地域で餌付けされていたのかもしれない.\n病院の先生の推測だと仔猫のときに猫風邪にかかっていて, 後遺症で右目の涙腺の機能が悪くなっている. そのため右目に目ヤニがたまりやすい.\nたぶんたいへんなにゃん生だった.\nそとちゃん, 保護される 2018年の6月頃, そとちゃんは保護猫活動をしている人に保護された.\n群馬のどこかの民家の軒下で, 産気づいた状態で見つかった. そのときの体重は3.3kg.\nちなみにそとちゃんという名前はその時つけられたもので, 家のそとにいたからそとちゃんなんだとか.\nその名付け方だと野良猫みんなそとちゃんじゃね?\n出産は大変な難産で, 仔猫は5匹くらいいたけども生きてお腹から出られたのはオスが2匹だけだった.\n産後はなんとかおっぱいもあげられて, 仔猫はなんとか育った.\nその後2匹の仔猫は揃って貰い手がみつかったが, 1匹はもらわれた先で今年の3月に病気で亡くなった.\nたいへんなにゃん生だった.\nそとちゃんと俺, 出会う その頃大学院に居た俺は猫動画中毒で, 就職したら自分でも猫を飼おうと考えていた.\n本当はブリーダーから譲渡してもらったり, 保護猫をもらいたいけど男の1人暮らしだと審査が通らないケースが多いと聞いて, あんまり良いイメージは無いけどペットショップかな\u0026hellip;と考えていたところに, 研究室の先輩(正確にはちょっと違う)の知り合いに猫を保護している人がいるのでどうか, という話をもらい今年の3月に一度面会することになった.\nその猫こそがそとちゃんである.\n仔猫を送り出した後に不妊手術を受けて里親待ちになったそとちゃんだったが, 2~3歳の成猫だったのでなかなか貰い手がみつからなかった.\n俺は1人暮らしでなかなかかまってあげられないことを考えるとおとなしい成猫がほしかったし, 何より自分に似て右目がちょっと小さいところに謎の親近感が湧いてしまったのでこの子を引き取ることにした.\nこの決断はほんとうに良かった.\nそとちゃん, うちに来る 引き取るといっても引っ越しやら入社やらですぐに引き取ることが難しかったので, そとちゃんは5月からうちで預かることになった.\n社会人1ヶ月目を生き延びながら帰りに寄り道したホームセンターや通販でそとちゃん用の爪とぎ, トイレ, お皿, おもちゃなんかを少しづつ買い揃えていくのはとてもワクワクした.\nそして2019年5月1日, 元号が平成から令和に変わる日にそとちゃんはうちにやってきた.\n最初の数分は前の家が寂しかったのかにゃんにゃん鳴いていたのに, 少し目を離したすきに俺の枕を占領してぐーすか寝ていた.\nもともと人間が好きなのもあって初日の夜から普通に足にすりすりしてくれたし, 用意したおもちゃで元気に遊んでくれたので本当にうれしかった.\nそんなこんなで, そとちゃんとの暮らしがはじまった.\n今のそとちゃん そとちゃんの現在の体重は4.1kg. 保護されたときに比べるとだいぶふっくらしている.\n貰う前はおとなしいと思っていたが, それは大きな勘違いだった. 超おてんば. 超アグレッシブ.\nおもちゃは遊び方が激しすぎてすぐ壊れるし, ちょっと噛み癖があるのでしょっちゅう噛んでくる.\n元野良なのに妙に舌が肥えていて, 気に入ったごはんしか食べてくれない.\nでもとてもかわいい. 仕事から帰るとにゃんにゃん言いながら出迎えてくれるので疲れが吹き飛ぶ.\nそとちゃんを引き取って本当に良かったと思う.\nそとちゃんにもこれからのにゃん生を幸せに生きてほしい.\nおわり 以上, うちにくるまでのそとちゃんについて書いてみた.\n次はそとちゃんの好きなものとか細かい特徴について書きたい.\n","date":"2019-08-24T00:19:26+09:00","image":"/post/2019-08-24-sotochan-story/2019-08-24-sotochan-on-my-bed.jpg","permalink":"/post/2019-08-24-sotochan-story/","title":"うちに来るまでのそとちゃん"},{"content":"Goに触りたい, だけどMacがない Go(golang)を勉強したい.\nしかし先日愛用していたMacがバッテリー自主回収プログラムに旅立ってしまったので当分帰ってこない\u0026hellip;\n仕方がないので, だいぶ前に買って家に転がっていたラズパイを使ってGoを動かせるようにしてみた.\n需要は無いと思うが一応作業ログとして残しておく.\nなにができるようになるか Raspberry Pi上で任意のversionのGoが動くようになる.\nなんでラズパイ? Macが帰ってくるまで待つのも退屈だから.\nなんでgoenv? 今日会社で教わったから.\nGoのバージョン管理ができるらしくて便利そうだから.\nラズパイには少し重そうなので若干不安ではある.\nつかうもの Raspberry Pi 3 Model B+ OSはRaspbian(10.0) shellはzsh いれるもの goenv (2.0.0beta11) repo 参考にしたもの goenv Installation ほとんどこの通りにやっただけ やること goenvのインストール Goのインストール Hello World goenvのインストール まずはgoenvのrepoを持ってくる.\nHOME直下に.goenvが作成され, その中にファイルが入ってくる.\n$ git clone https://github.com/syndbg/goenv.git ~/.goenv 次にgoenvを使うために必要な環境変数を設定ファイルに追加していく.\n今回はzshを使っているので.zshenvに書いていく.\n$ echo \u0026#39;export GOENV_ROOT=\u0026#34;$HOME/.goenv\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.zshenv $ echo \u0026#39;export PATH=\u0026#34;$GOENV_ROOT/bin:$PATH\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.zshenv $ echo \u0026#39;eval \u0026#34;$(goenv init -)\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.zshenv $ echo \u0026#39;export PATH=\u0026#34;$GOPATH/bin:$PATH\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.zshenv $ exec $SHELL exec $SHELLが問題なく動けばgoenvを使う準備は完了.\n`.zshenv` # # Defines environment variables. # ... # goenv export GOPATH=\u0026#34;$HOME/go\u0026#34; export GOENV_ROOT=\u0026#34;$HOME/.goenv\u0026#34; export PATH=\u0026#34;$GOENV_ROOT/bin:$PATH\u0026#34; eval \u0026#34;$(goenv init -)\u0026#34; export PATH=\u0026#34;$GOPATH/bin:$PATH\u0026#34; Goのインストール さっそくgoenvをつかって使用可能なGoのバージョン一覧を取得し, 最新の1.12.7をインストールした.\n$ goenv install -l \u0026gt; Available versions: \u0026gt; 1.2.2 \u0026gt; 1.3.0 \u0026gt; 1.3.1 \u0026gt; ... \u0026gt; 1.12.7 # これを入れる \u0026gt; 1.13beta1 $ goenv install 1.12.7 $ goenv versions \u0026gt; 1.12.7 $ goenv global 1.12.7 # すべての場所でGo 1.12.7を使うよう設定 $ go version \u0026gt; go version go1.12.7 linux/arm Hello World 本当にGoが動くかどうか検証する.\n$ mkdir -p $GOPATH/bin/hello-world $ cd $GOPATH/bin/hello-world $ vim hello.go $ go run hello.go \u0026gt;Hello, World! \u0026gt;from go(goenv) on Raspberry Pi できた.\n`hello.go` package main import ( \u0026#34;fmt\u0026#34; ) func main() { fmt.Println(\u0026#34;Hello, World!\u0026#34;) fmt.Println(\u0026#34;from go(goenv) on Raspberry Pi\u0026#34;) } おまけ 次はねこについての記事を書きたい.\n","date":"2019-08-15T22:09:21+09:00","image":"/post/2019-08-15-install-goenv-on-rasberry-pi/sotochan.jpg","permalink":"/post/2019-08-15-install-goenv-on-rasberry-pi/","title":"Raspberry PiにgoenvでGoの環境構築をした"},{"content":"ブログをつくった ブログをつくった.\n特に大きな目的があるわけではないが学んだことのアウトプットに使ったりうちのかわいいネッコについて書いたり趣味について書いたりしたい.\nつまりなんでも書き残したい.\nGitHub PagesとHugoでブログをつくった さっそくアウトプットの練習として今回ブログを作った手順を書いていく.\n突然記憶喪失になったときのためになるべくわかりやすく書きたい.\nなんでGitHub Pages? 無料だし, エンジニアっぽくてかっこいいから(偏見).\nなんでHugo? 参考になる記事が多めだったから.\nあと人気っぽかったから.\nつかうもの MacBook Pro (Retina, 15-inch, Mid 2015) macOS Mojave 10.14 brew(Homebrew 2.1.9) homebrew GitHubのアカウント(uzimihsr) GitHub Hugo v0.56.3 (後でインストールする) Hugo git version 2.17.2 テキストエディタ(何でも良い, 今回はVimを使った) ちょっとだけGitの操作 そこそこターミナルの操作 参考にしたもの GitHub Pages Hugo quick start Hugo hosting on GitHub ブログを作るまでの手順 GitHub repositoryを作成 Hugoでページを作成する GitHub Pagesでページを公開する 記事を追加する サンプルページから脱却する GitHub repositoryを作成 GitHubで新たにblogという名前のリポジトリを作成する.\nこのrepoはHugoのプロジェクトを置くのに使う.\n今回作った例 : uzimihsr/blog.git\n同様に, username.github.ioリポジトリを作成する.\nusernameはアカウント名で置き換える.\nここに配置したファイルがGitHub Pagesとして公開される.\n例 : uzimihsr/uzimihsr.github.io\n以上2つのリポジトリを使ってブログページを作っていく.\nHugoでページを作成する Hugoをインストールして, サンプルページを作ってみる.\n以下, ターミナルで操作する.\nまずはHugoをインストール.\n$ brew install hugo 適当な作業用ディレクトリに移動(自分の場合は~/Workspaceを使う),\nhugoで新規プロジェクトを作成する.\nWorkspace/blogディレクトリが作成される.\n$ cd ~/Workspace $ brew install hugo $ hugo new site blog $ cd blog Theme(いい感じのテンプレートみたいなもん)をインストールする.\nThemeの一覧はここで見られるが, Beautiful Hugoが良さそうなのでこれを使う.\ngit submodule addでthemes/beautifulhugoにBeautiful Hugoのリポジトリを追加する.\n他のThemeでもたぶん同じような操作でいけるはず.\n$ git init $ git submodule add https://github.com/halogenica/beautifulhugo.git themes/beautifulhugo Beautiful Hugoは親切なのでサンプルページ(exampleSite)を用意してくれている.\n今回はそのまま使うので全部blog直下にコピーする.\nコピーできたら, 早速ローカルで確認するためにhugo serverを立ち上げる.\nhttp://localhost:1313/ をブラウザで開くとexampleSiteのページが確認できる. 便利.\nだいたいわかったらCtrl+Cでhugo serverを止める.\n$ cp -r themes/beautifulhugo/exampleSite/* . $ hugo server -D 次はいよいよ実際にページをビルドしてGitHub Pagesに公開する.\nが, その前に設定ファイルをいじっておく.\nconfig.tomlに指定した情報をThemeが読み込んでいい感じのページを生成してくれているらしいので, 自分用にいろいろ変更する.\n$ vim config.toml config.toml\nbaseurl = \u0026#34;https://uzimihsr.github.io\u0026#34; DefaultContentLanguage = \u0026#34;en\u0026#34; title = \u0026#34;meow.md\u0026#34; theme = \u0026#34;beautifulhugo\u0026#34; metaDataFormat = \u0026#34;yaml\u0026#34; pygmentsStyle = \u0026#34;trac\u0026#34; pygmentsUseClasses = true pygmentsCodeFences = true pygmentsCodefencesGuessSyntax = true author = false [Params] subtitle = \u0026#34;にゃーん\u0026#34; logo = \u0026#34;img/avatar-icon.png\u0026#34; # Expecting square dimensions favicon = \u0026#34;img/favicon.ico\u0026#34; dateFormat = \u0026#34;January 2, 2006\u0026#34; commit = false rss = false comments = true readingTime = false wordCount = false useHLJS = true socialShare = true delayDisqus = true showRelatedPosts = true [Author] name = \u0026#34;uzimihsr\u0026#34; github = \u0026#34;uzimihsr\u0026#34; twitter = \u0026#34;uzimihsr\u0026#34; [[menu.main]] name = \u0026#34;Blog\u0026#34; url = \u0026#34;\u0026#34; weight = 1 [[menu.main]] name = \u0026#34;Tags\u0026#34; url = \u0026#34;tags\u0026#34; weight = 2 [[menu.main]] name = \u0026#34;About\u0026#34; url = \u0026#34;page/about/\u0026#34; weight = 3 GitHub Pagesでページを公開する ローカルのblogディレクトリにリモートのblogリポジトリを紐付け,\nさらにblog/publicディレクトリにuzimihsr.github.ioを紐付ける.\nHugoでページをビルドするとblog/publicディレクトリに必要なファイルが吐き出されるので,\nこれをuzimihsr.github.ioにpushすることでGitHub Pagesが更新されていく.\n$ git remote add origin https://github.com/uzimihsr/blog.git $ git submodule add -b master https://github.com/uzimihsr/uzimihsr.github.io.git public いろいろごちゃごちゃしてきたので, かんたんに記事を更新できるようにスクリプトdeploy.shを書く.\nこのスクリプトはhugoコマンドでblogディレクトリの内容を元に静的ページをビルドし,\nblog/public内に生成されたファイルをまとめてuzimihsr.github.ioリポジトリにpushしてくれる.\n$ vim deploy.sh $ chmod +x deploy.sh deploy.sh\n#!/bin/sh # If a command fails then the deploy stops set -e printf \u0026#34;\\033[0;32mDeploying updates to GitHub...\\033[0m\\n\u0026#34; # Build the project. hugo # if using a theme, replace with `hugo -t \u0026lt;YOURTHEME\u0026gt;` # Go To Public folder cd public # Add changes to git. git add . # Commit changes. msg=\u0026#34;rebuilding site $(date)\u0026#34; if [ -n \u0026#34;$*\u0026#34; ]; then msg=\u0026#34;$*\u0026#34; fi git commit -m \u0026#34;$msg\u0026#34; # Push source and build repos. git push origin master 作成したスクリプトを使ってページをビルドし, GitHub Pagesにpushする.\nすなわち, ブログを公開する. 更新されるのに少し時間がかかるが,\nhttps://uzimihsr.github.io をブラウザで開くとローカルで確認したものと同じページが確認できる.\n$ ./deploy.sh \u0026#34;Initial commit\u0026#34; 記事を追加する せっかくなので新しく記事を追加する.\nblog/content/postにMarkdownが追加されるので, これをいい感じに編集する.\n一番上のテーブルにあるdraft: false(下書き設定)をdraft: trueに変えると記事が公開される.\n記事を更新したら, スクリプトを使って更新した情報をGitHub Pagesに反映させる.\n$ hugo new post/2019-08-07-create-blog-1.md $ vim content/post/2019-08-07-create-blog-1.md $ ./deploy.sh \u0026#34;2019-08-07-create-blog-1.md\u0026#34; サンプルページから脱却する サンプルページのままだといらない記事があるので, 下書き設定(draft: false)に変更する.\n$ vim content/post/2015-01-04-first-post.md # 他の記事に対しても同様 $ ./deploy.sh \u0026#34;サンプル記事を非公開にした\u0026#34; また, 日本語のフォントが気に食わないのでblog/themes/beautifulhugo/static/css/main.cssをいじる.\nfont-family: 'Lora', 'Times New Roman', serif;となっている行を\nfont-family: 'arial', sans-serif;に変更する.\n$ vim themes/beautifulhugo/static/css/main.css $ ./deploy.sh \u0026#34;フォントを変更した\u0026#34; 注意すべきなのは, submoduleを編集しているためにこの変更をgitで管理できないこと.\n少し気持ち悪いので本当はBeautiful HugoをForkしてくるべき. あとで気が向いたらやる.\nアイコンも自分用に変更する.\nblog/static/images/にsotochan.jpgを配置して,\nconfig.tomlのlogo = \u0026quot;img/avatar-icon.png\u0026quot;となっている部分を\nlogo = \u0026quot;/images/sotochan.jpg\u0026quot;に書き換える.\n$ mkdir static/images $ cp ~/Desktop/sotochan.jpg static/images $ vim config.toml $ ./deploy.sh \u0026#34;アイコンを変更した\u0026#34; トップページとaboutページを編集する.\n特にトップページに表示したいものも無いのでcontent/_index.mdを削除する.\nまた, content/page/about.mdを好きに編集する.\n$ rm content/_index.md $ vim content/page/about.md 以上でだいたいブログのセットアップは完了.\nあとは3日坊主にならないよう頻繁に書いていきたい.\n","date":"2019-08-07T23:20:05+09:00","permalink":"/post/2019-08-07-create-blog-1/","title":"GitHub PagesとHugoでブログをつくった"}]