【AWS】Docker Composeを使ったGoのアプリを無料枠のEC2にデプロイした話

# はじめに

　どうも、Shakkuです。
　都内某高専情報科の3年生です。(2023/10/21時点)
　以前、[Go+Echo+GORM(mysql)+DockerでSession,JWT付きログインWebアプリ](https://qiita.com/Shakku/items/2dbf455dc270cc514436)というものを作成しました。
　現状、自分のMacBookAirで起動してlocalhostでのみアクセスできる状態になっています。
　そのため、作成したアプリで遊んでみたいときに毎回自分のMacBookでサーバーをたてて、同じLANに入って...という作業をしており、めんどくさいなーというのと、MacBookに負荷がかかるなーと毎回感じていました。

　そんな時に近々再度AWSを触るタイミングがやってきたので、ちょっとした復習も兼ねて今回は自身で作成したこのアプリケーションをEC2にデプロイしてみました。

　今回はお試しということで、費用をかけずに無料枠だけを使用してやってみました。

　(高専の授業でAWSを触った際は、アベイラビリティーゾーン,パブリック/プライベートサブネット,IGW,ロードバランサ,ElasticIPなどなど、可用性がありセキュリティもちゃんとした構築を作っていましたが、配られた１００ドルがどんどん減っていって悲しかったので、今回は無料枠だけの悲しい構築です...)


# 概要
　今回の構築は以下のようなセキュリティガバガバで可用性のない貧乏構築です。
![ec2-echo-app.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2998568/08a0a061-64ac-94bf-66b1-d7ec3cb20a8a.jpeg)

　EC2回りだけ触りたいので、インターネットゲートウェイとかVPCとかサブネットとかはデフォルトのものを基本的に使っちゃってます。

　AWSのコンソールへの登録やログイン方法に関しては省略しています。
　今回は私は学校から配布されたAWS Academyのアカウントを使用していますが、通常のアカウントで構築する際は、アカウントを作成してから12ヶ月のみ無料枠を使用できるそうなのでご注意を。(https://aws.amazon.com/jp/free/?all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=*all&awsf.Free%20Tier%20Categories=*all)

　リージョンもどこでも問題なさそうではあるが、今回はデフォルトのバージニア北部を使用しました。

　**(追記：アクセス元のリージョンと異なるリージョンで作成すると、リージョン間の通信によって少量の料金が発生してしまうようです。今回使用しているAWSAcademyのLearner Labでは、us-east-1とus-west-2しか使用できないため、少量の料金が発生してしまっています。アメリカに移住するしかないか...)**
[そんなに！？意外と知らないAWSでお金がかかるポイント5選](https://dev.classmethod.jp/articles/5-ways-to-spend-cost-on-aws/)


# 構築
## EC2作成
　サービスからEC2を選択して、インスタンスからインスタンスを起動を選択する。
　お好みの名前を入力し
![スクリーンショット 2023-10-19 10.19.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2998568/3f904f9a-26f8-7969-55e9-555dc88e731c.png)

　マシンイメージ(AMI)は`Amazon Linux`の無料枠のものを使用する。
![スクリーンショット 2023-10-19 10.20.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2998568/aa0beb53-32b8-ad5d-fc4a-243749df6d60.png)

　インスタンスは無料枠の`t2.micro`を使用する。(1CPU,1GBメモリなので、そのままだとDocker Composeを動かすには物足りないが、後程手を加える)
![スクリーンショット 2023-10-19 10.23.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2998568/4adedb8e-3f42-e632-6afa-b77ddb12ad3b.png)

　インスタンスに接続するためのキーペアを、新しいキーペアの作成から作成する。
![スクリーンショット 2023-10-19 10.23.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2998568/4eee0506-98c1-5cff-9dc6-f6ccff87489c.png)

　作成したらダウンロードして、ダウンロードした秘密鍵を/.sshに移動して、パーミッションを設定しておく
```bash
$ cp ~/Downloads/echo-login-app-server-key.pem ~/.ssh/
$ chmod 400 ~/.ssh/echo-login-app-server-key.pem
```

　ネットワーク設定から編集を押して、VPCはデフォルトのものを使っちゃう。
　サブネットもデフォルトのものを使っちゃう(どのアベイラビリティーゾーンでも問題ないが、今回はus-east a1を使用した。)
　パブリックIPは自動で割り当てしてもらう。
![スクリーンショット 2023-10-19 10.53.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2998568/d2c8fcad-2851-5a5e-9d10-6caba9efddd2.png)

　セキュリティグループを作成するを選択し、お好みの名前を入力

　インバウンドルール(EC2へのアクセスを許可するポートやプロトコルの設定)を基本的な`ssh:20` `HTTP:80` `HTTPS:443`の他に、今回デプロイするプログラムで使用している`8081` `8082`ポートも入れておく。
　基本的にはどこからでもアクセス許可しているが、apiで使用している`8081`ポートは、アプリケーション内でしか使用しないため、自分のIPにしている。
![スクリーンショット 2023-10-19 11.38.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2998568/ad1d6d24-8398-9f35-81ce-f6d31f6c3872.png)
![スクリーンショット 2023-10-19 11.38.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2998568/cf95ab52-d284-8f51-dced-39ba49a60591.png)
![スクリーンショット 2023-10-19 11.39.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2998568/0fee9a03-4bc1-f3df-142f-6065b26c7b32.png)

　ストレージはデフォルトのまま。

　高度な詳細から、念の為`終了保護を有効化`して間違えて吹っ飛ばさないようにした。
![スクリーンショット 2023-10-19 12.10.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2998568/7a6871f4-60ee-f46c-4003-4199c16d175d.png)

　そして、インスタンスを起動！

　インスタンスの一覧から、作成したインスタンスが緑の実行中になったら、起動完了。

## EC2の環境構築
### 1.起動したEC2にsshする。
　インスタンスの一覧から、起動したインスタンスのチェックボックスをクリックして、下の詳細をみてみると、`パブリック IPv4 アドレス`というものがある。
![スクリーンショット 2023-10-19 12.16.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2998568/64347b5d-0a40-72ee-899f-7b1833080b93.png)

　そのIPへ以下のコマンドでsshした。
```bash
$ ssh -i ~/.ssh/echo-login-app-server-key.pem ec2-user@パブリックIP
```

### 2.Dockerのインストール
　以下のコマンドを実行。
```bash
$ sudo yum install -y docker          # インストール
$ sudo service docker start           # 起動
$ sudo usermod -a -G docker ec2-user  # ec2-userをDockerグループに入れる。(これによって、dockerコマンドを実行できる。)
```

### 3.Docker Composeのインストール
　以下のコマンドを実行。
　私のローカル環境のDocker Composeのバージョンが`v2.18.1`だったので、このバージョンでインストールした。
```bash
$ sudo mkdir -p /usr/local/lib/docker/cli-plugins
$ sudo curl -SL https://github.com/docker/compose/releases/download/v2.18.1/docker-compose-linux-x86_64 -o /usr/local/lib/docker/cli-plugins/docker-compose
$ sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
$ docker compose version # バージョン確認
```

### 4.gitのインストール
　以下のコマンドを実行
```bash
$ sudo yum install git
```

### 5.作成したプログラムのclone
　今回デプロイするプログラムをGithubからcloneしてきた。
```bash
$ git clone https://github.com/Shakkuuu/echo-login-app
```

### 6.スワップメモリの増加
　今回の肝であるスワップメモリの増加。
　無料枠のt2.microでは、メモリが少なくDocker Compose実行時などのメモリを多く使用する処理を行うと落ちてしまう。(これでずっと起動できなくて悩んでた。)
　そこで、スワップメモリを増やして落ちるのを回避する。
```bash
# 今回は2GBのスワップファイルを作成
sudo dd if=/dev/zero of=/swapfile bs=1M count=2048
# スワップファイルに読み書き権限付与
sudo chmod 600 /swapfile
# スワップ領域のセットアップ
sudo mkswap /swapfile
# スワップ領域を有効化
sudo swapon /swapfile
# スワップ領域の確認
sudo swapon -s
```

実際にコマンドで確認してみると、しっかりメモリとして認識されていることがわかる。(以下は一部コンテナ起動済みの出力結果)
```bash
[ec2-user@ip-172-31-95-82 ~]$ sudo swapon -s
Filename				Type		Size		Used	Priority
/swapfile                               file		2097148		1792	-2
[ec2-user@ip-172-31-95-82 ~]$ free
               total        used        free      shared  buff/cache   available
Mem:          972188      549576       65836        1800      356776      279660
Swap:        2097148        1792     2095356
```

また、スワップを起動時に有効にするように追記する
```bash
$ sudo nano /etc/fstab

# 末尾に以下の一行を追加
/swapfile swap swap defaults 0 0
```

## Docker Composeの実行
いよいよDocker Composeでアプリの起動!

　`docker compose up -d`で起動!
　ちゃんと落ちないで起動できた。
```bash
[ec2-user@ip-172-31-95-82 echo-login-app]$ docker stats --no-stream
CONTAINER ID   NAME                  CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O       PIDS
111b2ee86291   echo-login-app-api    0.00%     66.25MiB / 949.4MiB   6.98%     96.9kB / 67.1kB   131MB / 222MB   12
2803a06ee682   echo-login-app-db-1   0.34%     195.7MiB / 949.4MiB   20.61%    44.4kB / 65.7kB   123MB / 211MB   38
dcdb4545dc45   echo-login-app-app    0.00%     61.71MiB / 949.4MiB   6.50%     33.8kB / 29.6kB   109MB / 211MB   12

```

　実際にEC2のパブリックIPでアクセスしてみると、
![スクリーンショット 2023-10-19 15.21.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2998568/886541c8-d651-6ae1-6315-2466381a72ce.png)

 同じLAN内でなくてもアクセスできた！

# 終わりに
　今回は、無料枠のEC2にGoで作成したアプリケーションをデプロイし、Docker Composeで起動してみました。
　自身のローカル環境でサーバーを起動せずとも、アプリケーションを使用することができるようになりました。
　無料枠のインスタンスであるt2.microではメモリが足りず、Docker Composeを実行すると落ちてしまうため、スワップメモリを増加させることで、解決しました。
　また、今回の構築ではパブリックIPアドレスを固定していないため、何かのタイミングでアドレスが変わってしまう可能性があったため、後日Elastic IPを使用して、パブリックIPアドレスの固定も行いました。
　しかし、今年7月ごろにパブリックIPアドレスの料金体系に関して変更があったため、現時点はこの構築では料金がかからないが、2024年2月より料金が発生してしまう可能性があるため気をつけておきたいです。[パブリック IPv4 アドレスの利用に対する新しい料金体系を発表 / Amazon VPC IP Address Manager が Public IP Insights の提供を開始](https://aws.amazon.com/jp/blogs/news/new-aws-public-ipv4-address-charge-public-ip-insights/)

# おまけ
　プロキシの設定などで、8082ポートにリモートでアクセスできない時があったので、ポート転送を設定してみました。
　今回は`firewalld`というツールを使用しました。
```bash
# インストール
$ sudo yum -y install firewalld
# 起動
$ sudo systemctl start firewalld
# 自動起動有効化
$ sudo systemctl enable firewalld
# 80ポートから8082ポートへのリダイレクト設定
$ sudo firewall-cmd --add-forward-port=port=80:proto=tcp:toport=8082
# 設定永続化
$ sudo firewall-cmd --runtime-to-permanent
# 設定確認
$ sudo firewall-cmd --list-all
...
  forward-ports: 
	port=80:proto=tcp:toport=8082:toaddr=
...

```

これによって、ブラウザでポートを指定しなくても(デフォルトの80番ポートで)ページが表示されました！

# 参考
- [AWS EC2インスタンスにdockerとdocker-composeをインストールして簡単なWEBサービスを立ち上げる方法 - Qiita](https://qiita.com/y-dobashi/items/e127211b32296d65803a)
- [EC2をt2.microで立てたときにやることメモ - Qiita](https://qiita.com/teracy164/items/c1dfc09901c7f242a13f)
- [5.9. ポート転送 - Red Hat Enterprise Linux](https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/security_guide/sec-port_forwarding)