AWS App Runner で、VPC Connector を使って Amazon RDS に接続してみた

# はじめに

 2022年2月8日(米国時間)に、AWS App Runner にて Amazon VPC 接続用の機能が追加されました。アップデート前は、VPC 内にホストされている Amazon RDS などのサービスに接続することが出来ませんでした。このアップデートにより、VPC 内にホストされている RDS をはじめとした、各種サービスと連携ができるようになりました！

AWS では、VPC 内でホストされているサービスがあるため、ユースケースが広がるアップデートとなります。詳細は次のブログを参照してください。

https://aws.amazon.com/jp/blogs/news/new-for-app-runner-vpc-support/



また、このアップデートの留意点としては、App Runner にアクセスする経路は変わらずインターネットをつかったアクセスが必要です。アクセス元 → App Runner 方向のインバウンドトラフィックは、インターネットを経由した接続が必要で、App Runner → なにか 方向のアウトバウンドトラフィックは、自分たちが管理している VPC の経路をつかった接続となります。詳細は次のブログを参照してください。

https://aws.amazon.com/jp/blogs/containers/deep-dive-on-aws-app-runner-vpc-networking/

![image-20220214024159630.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/bad9417c-4788-f67d-d545-85dbbb045c50.png)



今回の記事は、Go 言語で作成した Docker Image を使って、VPC 内に構成している RDS for MySQL と接続する App Runner を作成してみる記事となります。





# Go言語で動作確認用プログラムを作成

MySQL と接続するための Go言語のプログラムをローカル環境で作成します。エラーハンドリングなどは省略しているので、本番環境での利用はダメです。ローカルで開発するため、接続先の MySQL の情報は環境変数化しています。

```go
package main

import (
	"database/sql"
	"fmt"
	"net"
	"net/http"
	"os"
	"time"

	_ "github.com/go-sql-driver/mysql"
)

var db *sql.DB

func handler(w http.ResponseWriter, r *http.Request) {
	// MySQL のデータを表示
	row := db.QueryRow(`select * from sample`)

	var id, name string
	row.Scan(&id, &name)

	println(name)

	fmt.Fprintf(w, name+"\n")
	fmt.Fprintf(w, "\n")

	// ローカルのIPアドレスを表示
	fmt.Fprintf(w, "IPアドレス一覧"+"\n")

	interfaces, err := net.Interfaces()
	if err != nil {
		fmt.Println(err)
		return
	}
	for _, inter := range interfaces {
		addrs, err := inter.Addrs()
		if err != nil {
			fmt.Println(err)
			return
		}
		for _, a := range addrs {
			if ipnet, ok := a.(*net.IPNet); ok {
				if ipnet.IP.To4() != nil {
					fmt.Println(ipnet.IP.String())
					fmt.Fprintf(w, ipnet.IP.String()+"\n")
				}
			}
		}
	}

}

func main() {
	// MySQL のコネクションを取得
	mysql_hostname := os.Getenv("MYSQL_HOSTNAME")
	mysql_port := os.Getenv("MYSQL_PORT")
	mysql_username := os.Getenv("MYSQL_USERNAME")
	mysql_password := os.Getenv("MYSQL_PASSWORD")

	var err error

	db, err = sql.Open("mysql", mysql_username+":"+mysql_password+"@tcp("+mysql_hostname+":"+mysql_port+")/sugi01")
	if err != nil {
		println("MySQL のコネクション接続に失敗")
		panic(err)
	}
	defer db.Close()

	db.SetConnMaxLifetime(time.Minute * 3)
	db.SetMaxOpenConns(10)
	db.SetMaxIdleConns(10)

	http.HandleFunc("/", handler) // ハンドラを登録してウェブページを表示させる
	http.ListenAndServe(":8080", nil)
}
```



ローカル環境では、Docker を使って MySQL を動かしている必要があります。詳細はこちらの記事に記載しております。

https://qiita.com/sugimount-a/items/a13ae09627309a05728e



ローカルで、MySQL を動かしたうえで、Go 言語のプログラムの動作確認をしていきます。

```
go run app.go
```



ブラウザからアクセスすると、MySQL から取得した「MySQLのデータ」と、コンテナに付与されている IP アドレス一覧が表示されます。

![image-20220214020450613.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/4c00cb5e-9022-9ba8-f6c6-6812805f0e9e.png)



Go言語のプログラムをBuildします。

```
go build -o app
```



app ファイルがビルドされました。

```
> ls -la
total 6576
drwxr-xr-x 2 ec2-user docker      76 Feb 14 01:31 .
drwxr-xr-x 5 ec2-user docker      74 Feb 14 00:00 ..
-rwxr-xr-x 1 ec2-user docker 6714375 Feb 14 01:31 app
-rw-r--r-- 1 ec2-user docker    1567 Feb 14 01:28 app.go
-rw-r--r-- 1 ec2-user docker      87 Feb 14 01:30 Dockefile
-rw-r--r-- 1 ec2-user docker     101 Feb 14 00:55 go.mod
-rw-r--r-- 1 ec2-user docker     179 Feb 14 00:10 go.sum
```



# ECR Repository 作成

これから Docker Image を作成していくのですが、Docker Image を保管するための ECR Repository を用意していきます。ECR の画面を開いて、Create Repository を押します。

![image-20220214014643992.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/0eb03d4e-42d1-c8d9-019f-bae9c0146198.png)



Repository 名を入力します。

![image-20220214014815414.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/73ee0dbc-cdf1-07f6-23c7-2481bdcfe9f4.png)



Create Repository を選択します。

![image-20220214014832045.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/d3246528-84c5-0027-fb00-6dba300109c4.png)



作成されました。

![image-20220214014933307.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/5b4ea81f-94d2-b89d-73eb-5f0d3f65e8f6.png)



# Docker Image Build

次に、手元の環境で Docker Image を Build していきます。次のように Dockerfile を作成します。

- ベースのイメージは、ECR Public にある Golang 1.16 のものを利用
- Build した Go 言語のプログラムを、`/workdir` に格納
- 8080 ポートを EXPOSE

```dockerfile
FROM public.ecr.aws/docker/library/golang:1.16

RUN mkdir /workdir
COPY app /workdir

EXPOSE 8080
CMD ["/workdir/app"]
```



作成した ECR Repository を指定して、Docker Build を行います。

```
docker image build -t xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/app-runner-testapp:0.0.1 .
```



作成した Docker Image を使って、動作確認をします。

- MySQL はローカルの Docker で稼働している前提 (172.17.0.2 の IP アドレスは、MySQL コンテナの IP アドレス)

```
docker run \
-it \
--env MYSQL_HOSTNAME=172.17.0.2 \
--env MYSQL_PORT=3306 \
--env MYSQL_USERNAME=root \
--env MYSQL_PASSWORD=mypassword \
-e BIND-ADDRESS=0.0.0.0 \
-p 8080:8080 \
xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/app-runner-testapp:0.0.1
```



ブラウザから動作確認をします。正常に稼働しています。

![image-20220214020546187.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/809b54bd-7e61-0b15-6738-ce49361d80fa.png)



# ECR に Push

正常に Docker Image が作成できたので、ECR に Push していきます。まず、Login をします。

```
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com
```



Login 後、ECR に Push を行います。

```
docker push xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/app-runner-testapp:0.0.1
```



ECR に Push されています。

![image-20220214020823610.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/ad352fde-d84d-60b2-5c15-7df34be3287e.png)



latest tag も付与して Push をします。

```
docker image build -t xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/app-runner-testapp:latest .
docker push xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/app-runner-testapp:latest
```

![image-20220214030554941.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/c9d1e633-7bb3-63fb-c7ca-d8ef31fbe984.png)





# RDS の準備

App Runner から接続するための、RDS for MySQL を作成しました。

![image-20220214021034385.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/044512da-f4d7-7b48-d3d0-f66b967db009.png)



これに、テストデータを入れていきましょう。

```
mysql -u admin -h single-rds01.chuxmuzmrpgx.ap-northeast-1.rds.amazonaws.com -p
```



テストデータを入れます。

```
CREATE DATABASE sugi01;
USE sugi01;

CREATE TABLE sample(
  id INT(11) NOT NULL AUTO_INCREMENT,
  name VARCHAR(10) NOT NULL,
  PRIMARY KEY (id)
);

INSERT INTO sample(name) VALUES ("RDSのデータ");
```



テストデータが格納されているか確認です。

```
mysql> SELECT * FROM sugi01.sample;
+----+-----------------+
| id | name            |
+----+-----------------+
|  3 | RDSのデータ     |
+----+-----------------+
1 row in set (0.00 sec)
```



# App Runner の作成

それでは、App Runner で Service を作成していき、ECR に Push したコンテナイメージを動かしていきましょう。Create service を選びます。

![image-20220214021428735.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/641a1800-ef6c-fa36-8081-b4b618301c31.png)



ECR 上で作成したコンテナイメージを指定します。

![image-20220214021658794.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/31c1b208-d37c-94d3-c050-c9a5b6efedd3.png)



Deployment settings は、Automatic にしておきます。これにより、latest イメージが更新されたときに、自動デプロイを行うようにします。

![image-20220214021733415.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/f152db15-f3dc-0b6a-bc1f-5cc72c680721.png)



Service の名前や、環境変数などを指定していきます。Go 言語のアプリケーションに渡すための、RDS の接続情報を与えましょう。

```
MYSQL_HOSTNAME single-rds01.chuxmuzmrpgx.ap-northeast-1.rds.amazonaws.com
MYSQL_PORT 3306
MYSQL_USERNAME admin
MYSQL_PASSWORD yourpassword
```

![image-20220214022021271.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/49d9e1a0-65c7-c172-1a29-0b582c1236cb.png)



Auto Scaling デフォルトのままにします。

![image-20220214022047082.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/33555d13-172d-c71f-c390-373a619641d7.png)



Health Check はデフォルトのままにします。

![image-20220214022100593.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/fd2408b7-e137-5ac3-732a-f895daa5dade.png)



Security もデフォルトのままにします。

![image-20220214022119672.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/6acbb5f3-05c3-4b33-512f-4f1be3693e1c.png)



Networking では、RDS と接続するために、VPC Connector を作成していきます。

![image-20220214022156317.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/973f634c-d661-b302-9d4e-f8732f6620e0.png)



VPC connector の名前や、接続する VPC, Subnet, Security Group などを指定します。

![image-20220214023223464.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/adb1a611-6e36-3ad4-6aea-8ddfa22723dd.png)



指定された状態です。

![image-20220214023243974.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/16fb2e67-c42f-c97e-83af-7becf856a5fe.png)



Next

![image-20220214023254390.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/cb19e6a3-06fb-f420-8601-349a48cf21ed.png)



Create & Deploy を押します。

![image-20220214023304982.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/0e71e397-aeb2-7e13-060e-426de33a7af0.png)



Service の Deploy が走ります。

![image-20220214023343280.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/b78302a0-b5af-707a-30b5-3186777010cd.png)



一定時間後、Deploy が完了します。

![image-20220214031400730.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/0caae4d0-9853-f5b1-5609-1ff8321a511a.png)



# アクセス確認

該当のドメインにアクセスすると、作成したコンテナイメージが正常に稼働していることがわかります。RDS と接続が出来ており、「RDSのデータ」と表示さされています。

![image-20220214031432175.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/a9743a47-1c10-756b-6184-a91652cf6de5.png)