bon now
ありのままの現実を書き殴る吐き溜め。底辺SEの備忘録。
https://www.bon10.dev/
2024-03-24T16:24:00+00:00
bon
2024-03-24T16:24:00+00:00
品質とかオシャレとか
https://www.bon10.dev/post/2024/03/quality-fashion-etc.html
2024-03-24T16:24:00+00:00
2024-03-24T16:24:00+00:00
blog
bon
<p>今年に入って睡眠の計測や運動量の計測をもっとやれるようにしたいなーと思い始めた。
というのも使っていたSONYのWena wristがボロボロになりカバーが剥がれきたので、新しいスマートバンドを買おうと思ったからである。<br>
色々悩んだけど安かったので <strong>Xiaomi Smart Band 8</strong> を購入した。
おかげさまで睡眠の計測や運動量の計測がかなりできるようになった。Youtubeでよくある有酸素運動系の動画も、それぞれがどのくらい実際に効果があるのかも計測できるので、かなりオススメ。例えばなかやまきんに君さんの動画の有酸素運動10分のものは、実際に計測しても8〜9分は有酸素運動としてカウントされるため、効果的だといえる。</p>
<p>また、スマートバンド購入の影響でちょっとだけランニングに勤しむようになったことで、買う服も少し基準が変わった。<br>
乾きやすさや動きやすさを重視しつつ、洗い替えが必要なので、値段と性能とのバランス感がとても重要になってきた。<br>
とりあえず形から入るタイプではあるので、ランニングシューズにHOKA ONE ONE CLIFTON9を購入。
こんな形でHOKAデビューすることになるとは思わなかった。5、6年遅れくらいかな?</p>
<p>その他ウェアとかも揃えなきゃいけないんだけど、今までは細く長く着ることを中心にハイブランドでも一般品でもデザインや素材感を最重視していた。
しかし、トレーニングウェアはそういうわけにはいかない。ある程度洗濯に強くなければならないし、汗や雨で濡れたりしても乾きやすい素材でなければならない。</p>
<p>……と、いうことを考えていたのだが、これはオシャレもソフトウェアサービスの品質も同じじゃないかと思ったのである。</p>
<h3 id="part-b11e061e243615be">保守ができなければ結局品質は低い</h3>
<p>どれだけ縫製が良くても、素材が良くても、正しいメンテナンスができなければ結局品質は低くなる。
本来の服がもつ品質がどれだけ高くても、それを保つことができなければ寿命は短くなる。<br>
スーツの管理について無知だった新卒の頃、スーツをほぼ毎週クリーニングに出しまくっていた。
その結果、スーツの寿命は短くなり、着るたびにゴワゴワした感じが残ってしまった。
春夏シーズンは汗をかきやすいので、1、2着のスーツを着回しながら毎週クリーニングするよりも、
いっそのこと5着くらい持っておいて、1週間に1度の着用&1ヶ月に1度のクリーニングというローテーションを作ったほうがスーツそのものの寿命も結果的に伸ばせる。コストパフォーマンスを考えれば1着にかけるスーツの値段は抑えたほうがより良いだろう。<br>
こういう知識を持っているかどうかで、品質の高い服をどう選ぶかの基準は変わってくる。</p>
<h3 id="part-69222d529a7af73">品質の高いソフトウェア = 保守しやすい</h3>
<p>ITにおけるソフトウェア開発の現場では、永遠の課題として「ソフトウェアの品質」が挙げられるだろう。
聞いたことがないというエンジニアはいないくらいだと個人的には思っている。</p>
<p>品質が高いソフトウェアとは何か?という問いに対して「バグが少ない」とか「顧客の要求を満たす」とか「メンテナンスがしやすい」とか「拡張がしやすい」とか、いろいろな答えがあるだろう。<br>
僕の中ではここ1年で「保守(メンテナンス)しやすい」という観点に注目するようになった。</p>
<p>なぜならば、服と同じようにどれだけ品質の高いと言われるソフトウェアでも、保守ができなければ結局品質は低くなるからである。そうなる原因は色々あるけど、ここまで述べた通り <strong>知識の差</strong> に起因することがある。</p>
<p>例えばDDD(ドメイン駆動設計)。DDDはソフトウェアの品質を高めるための手法の一つであるが、知識がないと中途半端にメンテナンスしづらいものが出来上がってしまう(作ってる本人も理解できていないせい)。<br>
また、よく「メンテしやすいプログラミング言語」とか「メンテしやすいフレームワーク」とか言われて採用の選択肢に挙がることがある。
これについても「弊社ではHaskellを使っているので、メンテナンスがしやすいです」という内容だけを切り取ってHaskellを採用する会社はそんなにいないだろう。</p>
<p>結局これらは組織の中での知識の共有が重要であり、それができていないと品質の高いソフトウェアを作ることは難しいということを示していると考える。<br>
ということで、品質の良いものを作るためには、自組織における知識の共有と、現在の知識レベル(あるいはプログラミングスキル)を認識することが重要そうである。</p>
<p>その他にも0→1フェーズや1→10フェーズとかでの品質についても、組織の状態によって変わってくるため、どこぞのブログで書いていることをそのまま真似るのではなく、自分たちの組織に合った品質の高いソフトウェアを作るための方法を考えることが重要だと思う。
……という当たり前のことに気づくまでに10年くらいかかったので、これからも色々と試行錯誤していきたい。</p>
2023年の振り返りと2024年の目標
https://www.bon10.dev/post/2023/12/summaryo-f-2023-and-goals-for-2024.html
2023-12-31T14:30:00+00:00
2023-12-31T14:30:00+00:00
blog
bon
<p>この記事を書き終える頃には年が明けている頃だろう。あけましておめでとう。<br>
さて、久しぶりの投稿とはなるが、今年の振り返りと来年の目標を書いていく。</p>
<h2 id="part-b9f46ab9e49074ec">今年の振り返り</h2><h3 id="part-6542944b142">仕事</h3>
<p>今年は(も?)仕事が忙しかった。仕事納も特になく今もまだまだやることはたくさんある。<br>
ただ、それなりに成長もできたなーという実感はとてもある。</p>
<p>例えば今年はGo言語でDDDを書くこともできたし、Terraformを再度組んでGCPをまた1つ理解した気分になったし、
TypeScriptも少し書けるようになった気がしている。
プライベートではPython+FlaskでTwitterもどきを作ってみたり、Qiitaで記事をいくつか投稿してみたりもした。</p>
<p>また、副業でも英語を使う機会があったり、Rubyを書く機会があったり、他にもエンジニア転職の支援をしたり、
プログラミングスクールでのメンターも継続してやったりと人の成長や学びの瞬間に関わることもできた。<br>
これらは継続して続けていきたい。</p>
<h3 id="part-6542ec571d2">脱毛</h3>
<p>今年始めた脱毛はすでにサービス内容は終了しアフターケアモードに入った。<br>
5回の施術プランだったんだけど、ヒゲは6回でだいぶなくなった。
他の毛はまだまだ産毛が残っているので、こっちは別途契約して減らしたいと思っている。まぁ5回でも十分減るので気になる人は早めにやったほうが良い。
(特にヒゲは年齢とともに白い毛が増えてくるので、脱毛レーザーの効果がなくなってしまう)</p>
<h3 id="part-3786fbd9dd656e4">筋トレ</h3>
<p>継続している。全体的に筋肉量は増えた気がする。体重は1年前と比べたら1〜2kg減量といった感じ。<br>
痩せるには「間食を減らす」ことが一番効果的なことは実感したので、来年はもう少しトレーニングに重点を置きつつも
感触を減らしながら筋肉量を増やし、体脂肪率を12〜13%までもっていきたい。</p>
<h3 id="part-6542f442539">英語</h3>
<p>Duolingoを継続している。今年もまた1年365日継続できた。ただ話せるようになってないので、今年は勉強方法や手段を増やしていきたいと思っている。</p>
<h3 id="part-c6c9debba3df6291">ファッション</h3>
<p>今年はいい感じにクローゼットを整理できた。<br>
欲しいものは大体買えたし、あとはウールの黒のスラックスかワイドパンツがあればいいかなと思っている。
トップスではウールのニットがほしい。ローゲージで畔編みのやつ。</p>
<p>また、素材にもわりとこだわりが出てきた気もする。<br>
冬の時期だと化繊(特にポリエステル)の肌着やタイツを着ると、肌のかゆみが悪化することがわかったので、
できるだけウール、綿、麻、シルクなどの天然素材を選ぶようにしている。
下着はすでに綿100%に切り替え済み。
冬の上着もウールがベター。
夏場も汗対策のために何気に麻やウールが良いということがわかったので、来年はウールのインナーもあと少し増やしたい。</p>
<p>ちなみに、僕が今気になってるブランドは YOKO SAKAMOTO、 AUBETT、my beautiful landlet、O Project、BODHIあたり。</p>
<h3 id="part-3786900d7119624">ゲーム</h3>
<p>スーパーマリオRPGやらLIVE A LIVEやら懐かしのゲームを買ったけどまだ終わってない(ソニックも)……。
かわりにApex Legendsはだいぶやってる。未だシルバーランク止まり。</p>
<p>ゼルダの伝説ティアーズオブザキングダムもやりたいなあ。</p>
<h3 id="part-37868fc0752eb84">アニメ</h3>
<p>2000年代のアニメを結構観てる。鋼の錬金術師(2009年版)も観たし、Schooldaysも観た。
ちょうど青春時あたりの頃の作品なのですげー懐かしい。まだまだ観なければならない作品は多いので今年も継続して消化していこう……。</p>
<h3 id="part-2a0cbd85babfb66d">サブスク</h3>
<p>去年はコーヒー豆のサブスクで定期的にコーヒーを買っていたのに加え、プロテインのサブスクも始めた。<br>
コーヒーは<a href="https://postcoffee.co/">PostCoffee</a>、プロテインは<a href="https://vitanote.jp/service/vitanote-for-protein">VITANOTE</a>。<br>
PostCoffeeは毎月3種類のコーヒー豆が75g届く。浅煎り〜深煎りまでバラエティに富んでいるし、豆の種類も豊富で飽きが来ない。<br>
VITANOTEは毎月プロテインが届くんだけど、たまに無料で味噌汁やらオートミールやらがついてくる。<br>
また、尿検査を定期的に実施することで自分に足りない栄養素を知ることができるし、それがプロテインに反映されるので健康に良い(と思う)。
筋トレのついでに買ってたゴールドスタンダードプロテインが不要になったのでそれはそれで良かった。</p>
<p>他にも今年発見したのは<a href="https://otomoni.beer/">Otomoni</a>というクラフトビールのサブスク。
定期的にIPAが飲みたくなるのでとても良さそうではあるが、2週間に1本くらいしか飲まないので月1プランがあれば助かるなぁ。
まぁ、自分で配送感覚を調整すればよいのだが……</p>
<h3 id="part-3786fb53f1f73cf">花粉症</h3>
<p>2024年の花粉はそんなに多くないらしいという前情報がありつつも、ついに舌下免疫療法を始めた。
最低でも2年は必要らしいので、来年も継続できるようにしたい。</p>
<h2 id="2024">2024年の目標</h2><h3 id="part-6542944b142">仕事</h3>
<p>今年もまた勝負の年。デリバリー速度とプロダクト価値を維持しつつガンガン攻める。
攻めつつも守りを固めるために、コードの品質を上げることも意識していきたい。誰かお願い……。</p>
<p>副業も継続して日々成長を続けていきたい。すべての点と点が繋がっていくように。</p>
<h3 id="part-13bbe4a4597c6e09">プライベート</h3>
<p>筋トレは前述どおり体脂肪率を12〜13%まで落とす。体重は維持。腹筋は割りたい。
英語は話せるようになる。まずは1日1英文みたいな感じで英語を書いて英語で考えるクセみたいなのをつけようと思う。</p>
<p>プログラミングも毎日GitHubで草を生やせるようにする。<br>
あと投資も2023年でもちょいちょい増資したのでここも継続して利益を出せるようにしていきたい。
そして、なんとかして車を買うための資金が出来上がればいいなと思う……。</p>
2023年10月を振り返る
https://www.bon10.dev/post/2023/11/2023-10-retrospective.html
2023-11-04T18:00:00+00:00
2023-11-04T18:00:00+00:00
blog
bon
<p>2023年10月の振り返りです。大事なのは「やり遂げる」。これだけ覚えておこう。</p>
<h3 id="google-search-console">Google Search Consoleでインデックスが減った</h3>
<p>8月だったか9月だったかにURLの規則を変更したこともあってかGoogle Search Consoleでインデックスが減った。<br>
そのためGoogleからのアクセスが減った。
元々わりとアクセスのあったバイクの記事も検索に引っかからなくなってしまった。
インデックスを再登録してみているものの、特に増えることがないので気長に待つか更新頻度と被リンク上げて価値のあるサイトにしていくほかなさそう。</p>
<h3 id="part-b8a99f4">服</h3>
<p>結局ちょいちょい買い足している……。パーカーとスウェットパンツをゲットした。<br>
あとは冬用の黒スラックスかな?それとスニーカー。個人的に真っ黒ブーム来てる。</p>
<h3 id="x">Xとその代替</h3>
<p>X(Twitter)でつぶやくことがなくなったぶん、自分だけのメモとして色々つぶやくためのサービスを作った。
そのため、Xの利用はほとんどが情報収集となっていて、それもプログラミングやエンジニアリングのことではなく、
全く関係ない分野のことが多い。手作り装飾品とかフィギュアとか絵とかゲームとかそういう類。</p>
<p>自分で文章書く頻度もだいぶ減ってきてるんだけど、そのあたりは自分でつぶやくサービスの内容を1ヶ月か1週間でGPTで要約し、
それをブログ風にGPTで書き換えるっていう仕組みでいい感じに過去ログを残せないかと考えている。</p>
<h3 id="part-2fc44d50eac69e5c">気になったこと</h3>
<p>最近仕事をしていて、自分の学校卒業後に初就職した会社での仕事っぷりについて考えることがあった。<br>
当時はだいぶ仕事を抱え込むことも多くわりと色々迷惑をかけたなーと。
今でも設計や要件周りのことを考えてる間は時間がかかるし文字を書くのが遅速なところがあるので意識しなきゃだめだなと思う。<br>
こんな状況でも今までそこまで大きなトラブルなくなんとかなってきたのは、そんな状況でありながらもとりあえず「やり遂げる」ことを意識していたからかもしれない。</p>
<p>やり遂げたらとりあえず是非を考えることができるし、そこからどうするかを考えることができる。次に繋がる施策を考えられる。
しかも自分だけでなく、周りの人にも結果から何かを伝えることができるし、それを受け取った人が次に繋げてくれることもある。<br>
だからやり遂げるのは大事。もう3行でもいいからドキュメントやコード書いて出す、それだけでいい。
0はたとえそこにどんな努力や苦労や困難があろうが、0は0でしかない。</p>
<h3 id="part-37868fc0752eb84">アニメ</h3>
<p>昔観れなかった(観てなかった)アニメでそれなりに有名だったり面白いと評判だったりするものを色々観ている。
ただ、観たことないアニメは観るのに時間がかかる(聞き取れなかったり場面が分からなかったりすると嫌だから巻き戻すことがあったり集中して観たりしてしまう)ので、そういうときは狩野英孝さんのYouTubeを観ている。</p>
最近の買い物やら状況
https://www.bon10.dev/post/2023/09/about-recent-shopping-and-other-situations.html
2023-09-12T19:00:00+00:00
2023-09-12T19:00:00+00:00
life
bon
<p>ここ最近買い物することが多かったので、それについて書く。<br>
また現在の自分の心境というか状況についても何か書き残しておこうと思ったので書く。</p>
<h3 id="part-b3b9547b5c74bb62">買い物 -自転車編-</h3>
<p>自転車に乗る際にヘルメットを着用するのが努力義務化されたので、ついにヘルメットを買った。
以前の職場で「安全は金で買うべし」と言われたのだが、今回はエントリーモデルという感じでお安めのやつにした。
といっても <strong>「MIPS」</strong> という安全性を高める技術が搭載されているので、それなりに良いもののはずだ。<br>
購入したのは <strong>「SPECIALIZED ALIGN II MIPS」</strong> というモデル。MIPS なのに 1 万円を切ってるのはすごすぎる。</p>
<p>最後まで <strong>GIRO ISODE MIPS</strong> と <strong>GIRO CORMICK MIPS</strong> とで悩んでた。<br>
決め手はフォルム。すでに国内では在庫がなく eBay でしか買えそうになかった。ISODEはユニバーサルフィットではあるが見た目が流線的でかっこいい。
しかしながら自分はわりと頭大きいので、そこがネックではあった。<br>
で、CORMICKは見た目が野暮ったくてあんまり好きじゃなかった。
ALIGN IIはその丁度中間みたいなフォルムで値段も安く、しかも国内在庫があったので悩むこと無く購入。
ついでに余った予算でcinelliのサイクルキャップも購入。</p>
<p>また、日焼けが気になるのとハンドルグリップのゴムの汚れが黒く手についてしまうのが気になったので、サイクルグローブも購入した。<br>
これは中華製のノーブランド品。とはいえリフレクターがついているので夜道では若干の安心感がある。</p>
<p>これだけ揃えて思ったことは、ちゃんとハンドルをグリップするとそれなりに自転車を漕ぐパワーが上がるということ。
ビンディングシューズを履くともっと良いというのは昔から聞いていたので、やはり装備品にもこだわったほうがいいよなーと感じた。
まぁ自分は街乗りしかしない予定なので今のところこのあたりで打ち止め(サイクルキャップはローテで使いたいのであと1つほしいところ)</p>
<h3 id="part-3ca0d50d51bfa7aa">買い物 -バイク編-</h3>
<p>20年ぶり?くらいにヘルメットを新調した。<br>
僕は今までジェットヘルメットを愛用してきており、ヘルメットを脱がなくとも飲み物が飲めたり会話が容易にできたりするのが気に入っていた。<br>
が、安全性という観点からフルフェイスヘルメットを検討することにした。
とはいえ最初からフルフェイスだと面白くないので、ジェットヘルメットとフルフェイスヘルメットの両方を使えるモデルを選ぶことにした。
このモデルのことを <strong>「モジュラーヘルメット」</strong> というらしい。日本だとなぜか <strong>「システムヘルメット」</strong> と呼ばれている。</p>
<p>バイクがフランス製なので、フランスのメーカーである <strong>「SHARK」</strong> のモジュラーヘルメットを購入した。
将来的にネイキッドバイクに乗り換える予定ではあるので、デザインもそれっぽく攻めてるものにした(笑)</p>
<p>所感だが、フルフェイスヘルメットだと下が見えないので視野がわりと狭くなるのと、頬の部分が密着しているので息苦しさを感じる。(アンパンマンみたいになる)
しかしながら最近のヘルメットはインカムのスピーカーを後付できるように耳の部分にくぼみがあるため、簡単にスピーカーを取り付けられる。
(ただし、僕の持ってるインカムのスピーカーの直径が若干大きく、収まらなかったのでヘルメットを被ってると若干耳が痛い)</p>
<p>ちなみにインカムは↓なんだけど、3時間以上音楽再生しても全然電池切れしそうな雰囲気がない。電池持ちだけはSONYのワイヤレスイイヤホンより優秀。<br>
https://www.amazon.co.jp/dp/B078HBQ71S</p>
<h3 id="part-2840b6840ba474ab">買い物 -洋服編-</h3>
<p>10数年振りに青山に寄れる時間を作れたので、僕の好きなブランドの直営店へ行ってきた。
当たり前だけど直営店なのでめちゃくちゃ大量の商品があり、眺めるだけで時間が過ぎていくわけ。<br>
しかしながら時間的な制約があったので、ゆっくり見ることもできない状況ではあった。
そういうときに限って店員さんがいい感じにおすすめしてくれるわけ。
ちょうど冬用のバイクや自転車向けの防寒アウターを探しており、おすすめされたものを値札も見ずに購入。(支払い時にちょっとビビった)</p>
<p>他にもこの夏は『ちょっといいもの」としてサマーウールのアイテムを追加したり、釣り編みのニット織りのTシャツを買ったりもした。
とりあえず今年・先年の洋服大量買いによる衣替えは完了したので、しばらくは買い物は控えようと思う。
ただし、スプラ3コラボアイテムは買う。</p>
<h3 id="part-65432df8bd7">近況</h3>
<p>最近は仕事が忙しく、休日もちょいちょい頑張ってる。ただ昔ほど頑張れなくなっており、疲れが溜まった日はあまり進捗が芳しくない状況が続いている。
体力を上げるために自宅トレも始めたし、プロテインも飲み始めたのだがこの有様。<br>
体力面だけでなく精神面も少しダメージを受けている感じはあるが、とはいえ今はそんなことを言っている時期ではないのでなんとかするしかない。</p>
<p>なんか、ここ数年ずっと同じ感じで生きてきていて、延々となぜなのか、どうすればよいのかと考える状態が長く続いている。</p>
<p>と、こんな状況だとTwitter(現X)に書くような何かは面白くもなんともないようなことしかないので、
最近は投稿すらしていない。というか自分用のTwitterみたいなのを作ってしまったこともあり、もうTwitterは辞めようかなーと検討しているところ。
もはや何を書けばいいかもわからないし、他人の自慢や愚痴、近況を見て面白いなと思う感情が薄れてきているのも感じている。
Instagramでファッションブランドやセレクトショップの投稿を見ている方が数倍楽しいことにも気づいてしまったのであった。</p>
完全自動化:Terraformを使用したCloud RunとCloud DNSのドメインマッピング
https://www.bon10.dev/post/2023/07/cloudrun-and-clouddns-integration.html
2023-07-01T09:35:00+00:00
2023-07-01T09:35:00+00:00
work
bon
<p>以前の職場ではCloud RunのドメインはLoad Balancer経由だったのだが、
今回はCloud Runを直接Cloud DNSにマッピングする「ドメインマッピング」を利用してみた。<br>
これらの作業を「完全に」Terraformで自動化することに(多分)成功したのでここに記録しておく。</p>
<h2 id="part-2a064f37de75b4c9">はじめに</h2>
<p>Cloud RunとはGoogle Cloudのコンテナベースのサーバーレスなマネージドプラットフォームである。
そしてCloud DNSは先日Google Domainが管理移譲を発表したところではあるが、
ドメインに設定するDNSをGoogle Cloud上で管理できるDNSゾーンサービスである。</p>
<p>Cloud Runはデフォルトでは<code>*.run.app</code>のドメインが割り当てられるが、
これを本番で使うには心もとない。
そこで<a href="https://cloud.google.com/run/docs/mapping-custom-domains?hl=ja#run">Cloud DNSとCloud Runのドメインマッピング</a>を利用することで独自ドメインでCloud Runを公開することができる。
どうせならこの一連の手続きをTerraformで完全に自動化したいと思ったのが今回の記事の内容。</p>
<p>なお、2023/6現在Cloud DNSとCloud Runのドメインマッピング機能はpre-GAなので制限付きであり、今後仕様変更される可能性もあることに注意。</p>
<h2 id="part-2b935faccc118bde">前提条件</h2>
<p>今回はCloud DNSを使うにあたり<strong>ドメインをGoogle Domainで管理することを前提としている。</strong>
Cloud DomainのGoogle Domainsからの管理移譲や他ドメインサービスからの移行に関してはTerraformでは実現できないので、
<a href="https://support.google.com/domains/answer/10050215?hl=ja">https://support.google.com/domains/answer/10050215?hl=ja</a>を参考に手作業する。<br>
他社ドメインサービスをそのまま利用するにしてもCloud DNSのNSレコードを手動で設定する必要があるので、結局Terraformでは対応不可であることに違いはない。</p>
<h2 id="terraform-cloud-run-cloud-dns">Terraformを使用したCloud RunとCloud DNSの統合</h2>
<p>Terraformの構成をすべて説明すると長くなるので、
ここではCloud RunとCloud DNSのドメインマッピングに関連するTerraformのコードのみを説明・記述する。
variables.tfやoutputs.tfの詳細は各自の環境で埋めてほしい。(この記事を理解できる方であれば問題ないはず)</p>
<h3 id="part-71db5bfebc7dac34">ディレクトリ構成</h3><div class="highlight"><pre class="highlight shell"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="rouge-code"><pre><span class="nb">.</span>
├── environments
│ ├── dev
│ │ ├── main.tf
│ │ ├── terraform.tfvars
│ │ └── variables.tf
│ └── prod
├── main.tf
└── modules
├── network
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
└── cloudrun
├── main.tf
├── outputs.tf
└── variables.tf
</pre></td></tr></tbody></table></code></pre></div><h3 id="network-cloud-dns">Network(Cloud DNS)の設定</h3><div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre># Cloud DNSのゾーン設定
resource "google_dns_managed_zone" "myapp" {
name = "zone-myapp"
dns_name = "mydomain.com."
visibility = "public"
dnssec_config {
state = "on"
}
cloud_logging_config {
enable_logging = true
}
}
</pre></td></tr></tbody></table></code></pre></div>
<p>Cloud DNSの設定は特に難しいことはなく、DNSSECとCloudLoggingを有効にしているくらい。</p>
<h3 id="cloud-run">Cloud Runの設定</h3>
<p>Cloud RunのTerraformの全体は以下のとおり。</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
</pre></td><td class="rouge-code"><pre># Cloud Runのサービス設定
resource "google_cloud_run_service" "default" {
name = var.cloudrun_service_name
location = var.region
# 重複エラーが起きるため、リビジョン名を自動生成し既存のものと重複しないようにする。
autogenerate_revision_name = true
template {
metadata {
annotations = {
"autoscaling.knative.dev/maxScale" = "1" #最小構成
"autoscaling.knative.dev/minScale" = "1"
"run.googleapis.com/vpc-access-connector" = var.vpc_connector_id
"run.googleapis.com/vpc-access-egress" = var.vpc_connector_egress
}
}
spec {
containers {
# 正しいイメージはCloud Buildでビルドしたものを指定するため、ここでは適当なものを指定
image = "us-docker.pkg.dev/cloudrun/container/hello:latest"
ports {
container_port = var.port
}
}
}
}
lifecycle {
ignore_changes = [
#デプロイするたびに差分が出るため無視
template[0].metadata[0].labels["run.googleapis.com/startupProbeType"]
]
}
}
# 未認証のアクセスを許可する設定
data "google_iam_policy" "noauth" {
binding {
role = "roles/run.invoker"
members = ["allUsers"]
}
}
resource "google_cloud_run_v2_service_iam_policy" "noauth" {
location = google_cloud_run_service.default.location
project = google_cloud_run_service.default.project
name = google_cloud_run_service.default.name
policy_data = data.google_iam_policy.noauth.policy_data
}
# Cloud DNSとCloud Runのドメインマッピング
resource "google_cloud_run_domain_mapping" "default" {
location = var.region
name = var.domain_name
metadata {
namespace = var.project
}
spec {
route_name = google_cloud_run_service.default.name
}
}
# ドメインマッピングで発生する各種DNSレコード情報を動的に収集
locals {
dns_records_A = [for rr in google_cloud_run_domain_mapping.default.status[0].resource_records : rr.rrdata if rr.type == "A"]
dns_records_AAAA = [for rr in google_cloud_run_domain_mapping.default.status[0].resource_records : rr.rrdata if rr.type == "AAAA"]
dns_record_WWW = [for rr in google_cloud_run_domain_mapping.default.status[0].resource_records : rr.rrdata if rr.type == "CNAME"]
}
# A、AAAAレコードがない場合はCNAMEレコードを生成
resource "google_dns_record_set" "www" {
count = length(local.dns_records_A) > 0 || length(local.dns_records_AAAA) > 0 ? 0 : 1
name = "${var.domain_name}."
type = "CNAME"
ttl = 3600
managed_zone = var.google_dns_managed_zone_name
rrdatas = local.dns_record_WWW
}
# Aレコードがある場合はAレコードを生成
resource "google_dns_record_set" "default_A" {
count = length(local.dns_records_A) > 0 ? 1 : 0
managed_zone = var.google_dns_managed_zone_name
name = "${var.domain_name}."
type = "A"
ttl = 3600
rrdatas = local.dns_records_A
}
# AAAAレコードがある場合はAAAAレコードを生成
resource "google_dns_record_set" "default_AAAA" {
count = length(local.dns_records_AAAA) > 0 ? 1 : 0
managed_zone = var.google_dns_managed_zone_name
name = "${var.domain_name}."
type = "AAAA"
ttl = 3600
rrdatas = local.dns_records_AAAA
}
</pre></td></tr></tbody></table></code></pre></div>
<p>簡単に解説する。</p>
<h3 id="cloud-run">Cloud Runのサービス設定</h3>
<p>まず <code>google_cloud_run_v2_service</code> ではなく <code>google_cloud_run_service</code> リソースを使っている理由は、<a href="https://github.com/hashicorp/terraform-provider-google/issues/14569#issuecomment-1548587892">terraform-provider-google/issues/14569</a>にて言及されているとおり、
v2だとリビジョン名を無視すると <code>terraform apply</code> 時に毎回差分が見つかってデプロイ対象になってしまうし、
無視しないとリビジョン名が重複するためデプロイに失敗するというデッドロックに陥るためである。</p>
<p>実際はコンテナイメージの指定( <code>image</code> )を変更することで回避できるが、
初回のデプロイ時に <code>image</code> に指定したイメージが存在しないと <code>terraform apply</code> が失敗してしまう。<br>
初回デプロイ時はイメージを先にArtifacts Registryに手作業でpushしておくと吉。</p>
<h3 id="part-ea8b216bf67b9479">未認証のアクセスを許可する設定</h3>
<p><code>data "google_iam_policy" "noauth"</code> 、<code>resource "google_cloud_run_v2_service_iam_policy" "noauth"</code> は、
Cloud Runを一般公開するための設定である。</p>
<h3 id="cloud-dns-cloud-run">Cloud DNSとCloud Runのドメインマッピング</h3>
<p>ここが本題。<br>
ドメインマッピング機能を利用すると、サブドメインを持たないCloud Runのサービスに対しては、
AレコードやAAAAレコードが生成され、CNAMEが存在しない。
対して、サブドメインを持つCloud Runのサービスに対しては、CNAMEレコードが生成され、A/AAAAレコードが存在しない。<br>
この仕様を利用して <code>locals</code> でDNSレコード情報を収集し、自動的にAレコードやAAAAレコード、CNAMEレコードを対象のDNSゾーンに登録するようにしている。<br>
参考にしたサイト: <a href="https://stackoverflow.com/questions/60479206/how-to-access-cloud-run-service-ips-from-terraform-pulumi-to-dynamically-creat">How to access Cloud Run service IPs from Terraform / Pulumi to dynamically create A records? - stackoverflow</a></p>
<p>参考サイトの仕組みでは、 <code>rrdatas</code> が空の場合でもレコードが生成されてしまうため、ドメインとサブドメインをもつCloud Runを同時にマッピングすることができなかった。
そこでTerraformの <code>count</code> を使って場合分けする仕組みを使っている。<br>
このコードはなんと <strong>ChatGPTが生成してくれている</strong>。さすがGPT-4。</p>
<div class="highlight"><pre class="highlight terraform"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
</pre></td><td class="rouge-code"><pre><span class="c1"># ドメインマッピングで発生する各種DNSレコード情報を動的に収集</span>
<span class="nx">locals</span> <span class="p">{</span>
<span class="nx">dns_records_A</span> <span class="p">=</span> <span class="p">[</span><span class="nx">for</span> <span class="nx">rr</span> <span class="nx">in</span> <span class="nx">google_cloud_run_domain_mapping</span><span class="p">.</span><span class="nx">default</span><span class="p">.</span><span class="nx">status</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">resource_records</span> <span class="err">:</span> <span class="nx">rr</span><span class="p">.</span><span class="nx">rrdata</span> <span class="nx">if</span> <span class="nx">rr</span><span class="p">.</span><span class="nx">type</span> <span class="err">==</span> <span class="s2">"A"</span><span class="p">]</span>
<span class="nx">dns_records_AAAA</span> <span class="p">=</span> <span class="p">[</span><span class="nx">for</span> <span class="nx">rr</span> <span class="nx">in</span> <span class="nx">google_cloud_run_domain_mapping</span><span class="p">.</span><span class="nx">default</span><span class="p">.</span><span class="nx">status</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">resource_records</span> <span class="err">:</span> <span class="nx">rr</span><span class="p">.</span><span class="nx">rrdata</span> <span class="nx">if</span> <span class="nx">rr</span><span class="p">.</span><span class="nx">type</span> <span class="err">==</span> <span class="s2">"AAAA"</span><span class="p">]</span>
<span class="nx">dns_record_WWW</span> <span class="p">=</span> <span class="p">[</span><span class="nx">for</span> <span class="nx">rr</span> <span class="nx">in</span> <span class="nx">google_cloud_run_domain_mapping</span><span class="p">.</span><span class="nx">default</span><span class="p">.</span><span class="nx">status</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">resource_records</span> <span class="err">:</span> <span class="nx">rr</span><span class="p">.</span><span class="nx">rrdata</span> <span class="nx">if</span> <span class="nx">rr</span><span class="p">.</span><span class="nx">type</span> <span class="err">==</span> <span class="s2">"CNAME"</span><span class="p">]</span>
<span class="p">}</span>
<span class="c1"># A、AAAAレコードがない場合はCNAMEレコードを生成</span>
<span class="k">resource</span> <span class="s2">"google_dns_record_set"</span> <span class="s2">"www"</span> <span class="p">{</span>
<span class="nx">count</span> <span class="p">=</span> <span class="nx">length</span><span class="p">(</span><span class="kd">local</span><span class="p">.</span><span class="nx">dns_records_A</span><span class="p">)</span> <span class="err">></span> <span class="mi">0</span> <span class="err">||</span> <span class="nx">length</span><span class="p">(</span><span class="kd">local</span><span class="p">.</span><span class="nx">dns_records_AAAA</span><span class="p">)</span> <span class="err">></span> <span class="mi">0</span> <span class="err">?</span> <span class="mi">0</span> <span class="err">:</span> <span class="mi">1</span>
<span class="nx">name</span> <span class="p">=</span> <span class="s2">"</span><span class="k">${</span><span class="kd">var</span><span class="p">.</span><span class="nx">domain_name</span><span class="k">}</span><span class="s2">."</span>
<span class="nx">type</span> <span class="p">=</span> <span class="s2">"CNAME"</span>
<span class="nx">ttl</span> <span class="p">=</span> <span class="mi">3600</span>
<span class="nx">managed_zone</span> <span class="p">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">google_dns_managed_zone_name</span>
<span class="nx">rrdatas</span> <span class="p">=</span> <span class="kd">local</span><span class="p">.</span><span class="nx">dns_record_WWW</span>
<span class="p">}</span>
<span class="c1"># Aレコードがある場合はAレコードを生成</span>
<span class="k">resource</span> <span class="s2">"google_dns_record_set"</span> <span class="s2">"default_A"</span> <span class="p">{</span>
<span class="nx">count</span> <span class="p">=</span> <span class="nx">length</span><span class="p">(</span><span class="kd">local</span><span class="p">.</span><span class="nx">dns_records_A</span><span class="p">)</span> <span class="err">></span> <span class="mi">0</span> <span class="err">?</span> <span class="mi">1</span> <span class="err">:</span> <span class="mi">0</span>
<span class="nx">managed_zone</span> <span class="p">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">google_dns_managed_zone_name</span>
<span class="nx">name</span> <span class="p">=</span> <span class="s2">"</span><span class="k">${</span><span class="kd">var</span><span class="p">.</span><span class="nx">domain_name</span><span class="k">}</span><span class="s2">."</span>
<span class="nx">type</span> <span class="p">=</span> <span class="s2">"A"</span>
<span class="nx">ttl</span> <span class="p">=</span> <span class="mi">3600</span>
<span class="nx">rrdatas</span> <span class="p">=</span> <span class="kd">local</span><span class="p">.</span><span class="nx">dns_records_A</span>
<span class="p">}</span>
<span class="c1"># AAAAレコードがある場合はAAAAレコードを生成</span>
<span class="k">resource</span> <span class="s2">"google_dns_record_set"</span> <span class="s2">"default_AAAA"</span> <span class="p">{</span>
<span class="nx">count</span> <span class="p">=</span> <span class="nx">length</span><span class="p">(</span><span class="kd">local</span><span class="p">.</span><span class="nx">dns_records_AAAA</span><span class="p">)</span> <span class="err">></span> <span class="mi">0</span> <span class="err">?</span> <span class="mi">1</span> <span class="err">:</span> <span class="mi">0</span>
<span class="nx">managed_zone</span> <span class="p">=</span> <span class="kd">var</span><span class="p">.</span><span class="nx">google_dns_managed_zone_name</span>
<span class="nx">name</span> <span class="p">=</span> <span class="s2">"</span><span class="k">${</span><span class="kd">var</span><span class="p">.</span><span class="nx">domain_name</span><span class="k">}</span><span class="s2">."</span>
<span class="nx">type</span> <span class="p">=</span> <span class="s2">"AAAA"</span>
<span class="nx">ttl</span> <span class="p">=</span> <span class="mi">3600</span>
<span class="nx">rrdatas</span> <span class="p">=</span> <span class="kd">local</span><span class="p">.</span><span class="nx">dns_records_AAAA</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div><h2 id="part-2a00357080695c85">おわりに</h2>
<p>今回はCloud RunのサービスをドメインマッピングするためのTerraformコードを紹介した。
Cloud Runのドメインマッピングはpre-GAの機能であるため、今後仕様が変更される可能性があるので恒久的にこの仕組みが使えるわけではない。<br>
また、それなりに規模が大きく信頼性の求められるプロダクトでは、ドメインマッピングを使わずにCloud Load Balancingを使うことが多いと思う。
そういう意味では、今回のコードはニッチなノウハウになるかもしれない。<br>
個人的には手作業の範囲を限りなく減らすことができて満足。</p>
<p>今回のコードもまた誰かの参考になれば幸いである。</p>
docker-composeを使ってレプリカセットでmongoDBを起動する
https://www.bon10.dev/post/2023/06/start-mongodb-with-replicaset-using-docker-compose.html
2023-06-28T13:00:00+00:00
2023-06-28T13:00:00+00:00
bon
<p>MongoDBのバージョンが4、5、6とあったりレプリケーションでのローカル起動の方法が色々あったり、正しい情報を探すのにとても苦労したので、
ここにdocker-composeを使ってmongoDBをレプリカセットで起動する方法をメモしておく。
ちなみになぜレプリカセットで起動したいのかというと、MongoDBの <strong><a href="https://www.mongodb.com/docs/manual/changeStreams/">Change Streams</a></strong> を使いたいたかったからである。 </p>
<h2 id="part-4c7000f1f70040f4">最初にまとめ</h2>
<p>最初にdocker-compose周りのディレクトリ構成を示す。</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre>.
├── docker
│ └── db
│ └── init
│ └── init.js
└── docker-compose.yml
</pre></td></tr></tbody></table></code></pre></div>
<p>次に作成するdocker-compose.ymlは以下のようになる。</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
</pre></td><td class="rouge-code"><pre>version: '3'
services:
mongo:
image: mongo:6.0.6
environment:
- AUTH=no
command: [--replSet, my-replica-set, --noauth, --bind_ip_all]
ports:
- 27017:27017
healthcheck:
#test: test $$(mongosh --port 27017 --quiet --eval "try {rs.initiate({_id:'my-replica-set',members:[{_id:0,host:\"mongo:27017\"}]})} catch(e) {rs.status().ok}") -eq 1
test: mongosh mongo-init.js
interval: 10s
start_period: 30s
volumes:
- mongodb_data:/data/db
#- ./docker/db/init/:/docker-entrypoint-initdb.d/:ro
- ./docker/db/init/db_init.js:/mongo-init.js
restart: always
mongo-express:
image: mongo-express
container_name: mongo_express
restart: always
ports:
- 8081:8081
environment:
#ME_CONFIG_MONGODB_URL: mongodb://@mongo:27017/
ME_CONFIG_MONGODB_ADMINUSERNAME: root
ME_CONFIG_MONGODB_ADMINPASSWORD: password
ME_CONFIG_MONGODB_SERVER: mongo
ME_CONFIG_MONGODB_PORT: 27017
depends_on:
- mongo
# mongodbのデータはdocker volumeで管理されるので、消すときは docker volume rm mongodb_data
volumes:
mongodb_data:
</pre></td></tr></tbody></table></code></pre></div>
<p>上記docker-compose.ymlに記載のある起動用のスクリプトも作成する。これはmongoDBのレプリカセットを初期化するためのものである。</p>
<div class="highlight"><pre class="highlight plaintext"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
</pre></td><td class="rouge-code"><pre>init = false;
print("Init script ...")
try {
if (!db.isMaster().ismaster) {
print("Error: primary not ready, initialize ...")
rs.initiate(
{
_id:'my-replica-set',
members: [
{ _id:0,
host: "mongo:27017"
}
]
}
)
quit(1);
} else {
if (!init) {
admin = db.getSiblingDB("admin");
admin.createUser(
{
user: "root",
pwd: "password",
roles: ["readWriteAnyDatabase"]
}
);
init = true;
}
}
} catch(e) {
rs.status().ok
}
</pre></td></tr></tbody></table></code></pre></div>
<p>以上でdocker-composeを使ってmongoDBをレプリカセットで起動することができる。<br>
次章より簡単にどういう実装内容なのかをまとめる。</p>
<h3 id="mongodb">MongoDB</h3>
<p>まず利用するMongoDBはバージョン6.0.6を利用。レプリケーションを利用したいので <code>--replSet</code> オプションをつけて起動する。<br>
今回はローカルでの開発用なので最小構成としてプライマリーノードを1つだけ起動する。本来は3台構成で起動するのが望ましい。<br>
<code>--bind_ip_all</code> はローカルでの開発用として外部からの接続を許可するためにつけている。</p>
<p>また、レプリカセットの初期化を行うために <code>healthcheck</code> にてスクリプトを監視するようにしている。<br>
MongoDBのコンテナイメージはレプリカセット起動をすると、<strong>entrypoint-initdb.dにあるスクリプトを実行してくれない。</strong>
この問題(仕様)により、レプリカセットの初期化やユーザーの作成を行うためにハック的に <code>healthcheck</code> でスクリプトを実行するようにしている。
考えた人は天才だと思う。<br>
スクリプト内で作成している管理ユーザーはmongo-express(GUIでMongoDBを操作できるアプリ)と自前のWebアプリのために作成している。
この設定によりわざわざ認証鍵を生成・指定する必要がなくなる。</p>
<p><code>volumes</code> をdocker volumeにしている理由は、パーミッションの問題で動かなかったため。</p>
<p>また上述通りコンテナの初回起動時レプリカセットが起動するタイミングでhealthcheckは必ずコケるため、 <code>restart: always</code> をつけている。</p>
<p>参考にしたサイト:<br>
* <a href="https://stackoverflow.com/questions/76013265/how-to-do-a-mongodb-6-single-node-replicaset-with-docker-compose">https://stackoverflow.com/questions/76013265/how-to-do-a-mongodb-6-single-node-replicaset-with-docker-compose</a></p>
<ul>
<li><a href="https://n-laboratory.jp/articles/mongodb-replicaset-docker">https://n-laboratory.jp/articles/mongodb-replicaset-docker</a></li>
<li><a href="https://github.com/docker-library/mongo/issues/339">https://github.com/docker-library/mongo/issues/339</a></li>
</ul>
<h3 id="mongo-express">mongo-express</h3>
<p>mongo-expressはmongoDBのGUIクライアントである。mongoDBの操作をGUIで行いたい場合に利用する。<br>
<code>environment</code> を使うことでmongoDBの接続先を指定することができるので便利。
このコンテナを使いたいがために色々docker-composeの記述方法を調べていたといっても過言ではない。</p>
<h2 id="part-2a00357080695c85">おわりに</h2>
<p>docker-composeを使ってmongoDBをレプリカセットで起動する方法をまとめた。
ここまで簡単・単純にまとまった記事は(僕が探した範囲では)なかったので、今後同じようなことをする人の参考になれば幸いである。<br>
多分MongoDBのバージョンが上がるとまた色々変わると思うので、その時はまた記事を書くかもしれない。</p>