趣味のサイトをAWSネイティブ化する挑戦
今までプライベートの開発ではHerokuやrender.comなどのPaaSでVCSを指定してアプリケーションソースを指定すればいい感じにデプロイしてくれる環境に頼り切っていました。
最近はモバイルアプリエンジニアからフルスタックエンジニアとして働き始めたので、この機会に学習を兼ねてAWS上にクラウドリフト&クラウドシフトしてクラウドネイティブ化してみることにしました。
今回題材にするのはRust製の温泉旅の記録ツールです。まだ開発途中ですが実際にアプリケーションとしては使用しているものになります。ただ開発すらもマンネリ化してきたのでアプリケーションサイズが小さいうちにクラウドネイティブ化します。
まず初めにクラウドリフトから行うことを考えます。
余談ですが、趣味で大規模アーキテクチャをいじることができるような環境は用意しておきたいので、費用に問題がない限りはEC2で大きめのサーバーを運用することは続けていこうかなと思っています。
(のちのちコンテナ化してFargateに切り替えたいですがお金はどうなんだろうというのが懸念)
まず最初に考えたのは、CodeDeploy等から一気にEC2を安全にデプロイできるようなCI/CDを組むことでした。
しかし、最初にやるには構成を想像するための経験や知識もないのが現状です。
無駄にAWSのサービスとその役割を知っているせいでこう組み合わせればいけるんじゃないかということだけはわかるものの、具体的にどうやって接続したり手順を踏んだりすればいいかがわからずモチベーションが数か月ずっと上がりませんでした。
そこで、車輪の再発明とは思いつつも愚直に一歩ずつ進めていくことにしました。
手順書はきちんとのこしつつ、その手順を自動化するという目的でAWSのサービスを増やすイメージで実装していきます。その方がうまみもわかりやすいので経験として積みやすいかなとも思います。
EC2の立ち上げ
まずはEC2インスタンスを立ち上げました。
ボタンポチで起動する系のPaaSはどういう要素があってウェブサーバーが立ち上がっているのかがそもそもブラックボックスされているので、普段クライアントサイドを仕事にしている人にとってはかなりありがたいのですが、こういう時に詰まるものです。
起動テンプレートの存在はしりつつも、我慢して愚直にEC2の情報を入力し、それをメモしながらサーバーが停止しても同じように設定すれば運用できるようにしています。
起動テンプレートをなんとなく設定して動いたとなるよりかは、起動テンプレートを学習するモチベーションが低すぎること、それまでにAWSを触るモチベーションが維持できないこと、従来のEC2のみの場合での運用と比較する方がありがたみがわかりやすいことと、EC2が万が一停止してサービスが使えなくなった場合にやっぱりAWSめんどくさいからやめとくかとならないような救済措置として用意するという脳死でもできるようにあえてEC2直でスピード感をもってやる状態に追い込みました。
VPCのセットアップ
EC2の立ち上げの際にVPCの指定が必要だったのでせっかくなので設定してみました。
サブネット、インターネットゲートウェイ、ルートテーブル、セキュリティグループと要素が急激に増えてあたふたしましたが、なんとなく立ち位置はわかりました。
(なんか、アタッチという概念って疎結合だから良いように見えて、そのリソースでなにができるかが見えなくなるのでそれはそれで辛いですね。)
EC2のセットアップ
VPCもきちんと設定して、さっそくSSHでログインします。
こいつが落ちてもちゃんと運用していくぞという強い意志で取り組みます。
Rust製なので、環境構築の記事はほとんどなく何とか頑張ってアプリケーションの立ち上げまで持っていくことができました。
ポイントだけ絞って書いておきます。
Amazon Linux2023へのmysqlのライブラリのインストールは癖が強い
下記が大変参考になりました。
そもそもリポジトリの中に該当のものが入っておらず、まずそれを探してきて、yum特有の名前のパッケージをインストールするという結構しんどい作業が発生しました。
具体的な問題は下記のとおりです。
前提条件として、gccのインストールとrustup show
は完了しているものとします。
cargo install diesel_cli --no-default-features --features "mysql"
を実行すると下記のエラーが発生します。
= note: /usr/bin/ld: cannot find -lmysqlclient: No such file or directory
collect2: error: ld returned 1 exit status
lmysqlclientと調べてもaptのやり方は書いてあるがyumのやり方は書いていなかったり、書いてあるとしてもリポジトリのインストールをすることが書いていなかったりと露頭に迷いかけました。
8000ポートで立ち上がった場合はポートの開放が必要
これはすぐわかりましたが、セキュリティグループのインバウンドルールで許可してあげれば問題なく導通するようになります。
SSHのログアウトをしたタイミングでアプリケーションが終了してしまう。
SSH接続した状態でアプリケーションを立ち上げる場合、nohupコマンドでHUPを無視するように起動できるみたいです。
Route53でドメイン名の取得
お名前.comよりも画面内の情報が少なくてわかりやすかったです。
眠いのであとでっかう
ALBにセキュリティグループがついていなかったので、導通しなかった。
今まではほぼ手動でec2インスタンスに直接ソースを取り込んでビルドしていたので作業が大変でした。
起動テンプレートのユーザーデータに書いて自動化したとしてもスクリプトの内容がインスタンスのスペックに耐えきれず途中でこけてしまうこともありました。
そのためビルドするときはインスタンスタイプをxlargeにしてビルドがうまくいったらmicroに戻して常時起動してソースに更新があればcargo run --releaseを毎回打つような運用でした。
そんな中、rocketがv0.5で大型リリースを迎え、nightly rustを脱却してstableになりました。
その変更を当アプリケーションに適用させるためにEC2インスタンスを試行錯誤していました。
そのため依存関係を変えずにいた運用では原因特定が困難になり、相当な時間を取られてしまいました。
そこで今回Dockerコンテナ化に挑戦しました。
差分を小さくするためAmazonLinuxのイメージを使って起動テンプレートの内容の内容をそのまま持ってきたものをDockerfileに記述し、Dockerhubにpushする運用に変えました。
それに合わせて起動テンプレートをdockerのインストールとpullとrunするだけに変えました。
この動作自体が軽量なのでmicroでもすぐ動いてくれました。
次回はGithub ActionsからDockerhubにpushするのを自動化します。その後はFargateに挑戦しようと思います。もし料金が増えるようだったらCodeDeployで試そうかなと。
Fargateに挑戦しましたが、まだまだよくわからなかったので悶々とした日々を過ごしていました。
なんとなくEC2 Auto Scalingを触っていたら、「インスタンス更新」なるものを見つけ、これを使うと最新の状態に入れ替えられることが分かりました。
今はこれをGithub Actionsに組み込み、mainブランチにプッシュされたときに自動的にALBに紐づいたインスタンスが最新のビルドに入れ替わるといったような状態を実現できました。
※AutoScalingから自動的にTargetGroupにスケールアウトするときのポート番号はターゲットグループを作る時点で設定しないとデフォルトの80になってしまうので、8000でリッスンするための作り直しが必要でした。