
WEBサーバをコンテナにした際に静的コンテンツはどこへ保存するのか
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
EC2で起動しているWEBサーバをコンテナ化する場合、静的コンテンツはどこへ保存するのか。 EC2内に静的コンテンツを持っていたならS3へ静的なコンテンツは移動し、CloudFront経由で配信するのがよくあるパターンでしょう。静的コンテンツをWEBサーバ以外から配信する際にWEBアプリケーションではどのような修正が必要になるのか。ぼやっとした理解なので画像の配信を例に検証してみました。

検証環境
下記の構成で実際にEC2からFargateに変更して試してみます。静的コンテンツの配信方法は他にもいろいろと構成は考えられますが、今回の検証ではELBと、CloudFrontに別々のサブドメイン割り当てたかたちとしました。

WEBアプリ
| 項目 | バージョン | 
|---|---|
| Go | 1.15.7 | 
| Echo | 4.2.0 | 
EC2
| 項目 | バージョン | 
|---|---|
| OS | Amazon Linux2 | 
| InstanceType | t4g.nano | 
| Architecture | Arm(Graviton2) | 
AWS Fargate
| 項目 | バージョン | 
|---|---|
| Copilot | 1.2.0 | 
| Fargate platform | 1.3.0 | 
WEBアプリ
WEBフレームワークはEchoを利用します。views/index.htmlと、assets/images/orora24O.jpgを読み込んでEC2ローカルに保存した画像を表示するだけページを作成しました。このアプリケーションをEC2インスタンスで実行しWEBサーバとして起動します。
手元でビルドしてEC2へ必要なファイルをコピーする計画です。ディレクトリ構成は以下です。
 $ tree
.
├── assets
│   └── images
│       └── orora240.jpg
├── go.mod
├── go.sum
├── main.go
└── views
    └── index.html
package main
import (
	"io"
	"net/http"
	"text/template"
	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
)
// Template render
type Template struct {
	templates *template.Template
}
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
	return t.templates.ExecuteTemplate(w, name, data)
}
func main() {
	// Echo instance
	e := echo.New()
	// Middleware
	e.Use(middleware.Logger())
	e.Use(middleware.Recover())
	// Load html file
	t := &Template{
		templates: template.Must(template.ParseGlob("views/*.html")),
	}
	e.Renderer = t
	// Routes
	e.GET("/", Home)
	// Static files
	e.Static("/img", "assets/images")
	// Start server
	e.Logger.Fatal(e.Start(":80"))
}
// Handler
func Home(c echo.Context) error {
	return c.Render(http.StatusOK, "index", "網走")
}
e.Static("/img", "assets/images")の記述により、assets/images配下のファイルは/imgのパスで参照できます。なので、画像の参照パスは/img/[ファイル名]になります。
{{define "index"}}
<html>
<body>
    <h1>ようこそ、 {{.}}へ</h1>
    <img src="/img/orora240.jpg" height="auto" width="auto">
</body>
</html>
{{end}}
Arm(Graviton2)インスタンスで実行するためクロスコンパイルします。
GOOS=linux GOARCH=arm64 go build -o webapp main.go
バイナリファイルwebappが作成されました。
EC2で画像配信

必要最低限のファイルをEC2へコピーしたディレクトリ構成です。
$ tree . ├── assets │ └── images │ └── orora240.jpg ├── views │ └── index.html └── webapp
バイナリファイルを実行します。80番ポートでリッスンし、ArmでもWEBアプリを起動できています。
$ sudo ./webapp
   ____    __
  / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.2.0
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                    O\
⇨ http server started on [::]:80
ALBの設定は省略します。下記作業を実施。
- EC2の前にALBを置きACMで作成した証明書を設定
 - HTTPS(443)でリクエストを受け付け、EC2へはHTTP(80)で通信
 - Route53で独自ドメインを設定
 
WEBアクセステスト
WEBブラウザからアクセスしEC2内に保存した画像を確認できました。

コンテナ化してCloudFrontから画像配信
WEBアプリをコンテナ化してAWS Fargateで起動させます。
S3に画像を保存とCloudFront設定

EC2で画像配信時には/img/のパスで画像へアクセスしていたため、S3のディレクトリ構成も同様にしました。
$ s3-tree abashiri-image
abashiri-image
└── img
    └── orora240.jpg
CloudFrontに独自ドメインを設定してS3の画像をCloudFront経由でアクセスできるように設定しました。

HTMLファイルの修正
画像のパスをローカルの画像ファイルではなくCloudFrontのURLに修正。今回の検証で確認したかったことはこの部分です。ローカルのパス指定から、CloudFrontのパスを指定するだけで済むのか。
- 変更前: 
/img/orora240.jpg - 変更後: 
https://image.abashiri.hoge/img/orora240.jpg 
{{define "index"}}
<html>
<body>
    <h1>ようこそ、 {{.}}へ</h1>
    <img src="https://image.abashiri.hoge/img/orora240.jpg" height="auto" width="auto">
</body>
</html>
{{end}}
WEBアプリの修正
ローカルの画像ファイルは使用しないためコメントアウト。コメントアウトしない場合assets/imageディレクトリをコンテナ内にコピーしないと指定したディレクトリがなく実行時エラーになります。
	// Static files
	// e.Static("/img", "assets/images")
Dockerfile作成
index.htmlファイルは必要なのでviewsディレクトリごとイメージ内へコピーします。ビルドしたバイナリファイルと同階層の/appへ配下を指定。画像ファイルはイメージ内に不要なのでasset/imagesディレクトリはコピーしていません。
FROM golang:1.15.8-alpine3.13 as builder RUN apk add --update --no-cache git WORKDIR /app COPY . . RUN go mod download RUN go build -o webapp . FROM alpine:3.13 RUN apk add --update --no-cache ca-certificates WORKDIR /app COPY --from=builder /app/webapp /app/webapp COPY ./views /app/views/ EXPOSE 80 ENTRYPOINT ["/app/webapp"]
ローカルアクセステスト
ビルドして確認してみました。
$ docker build -t webapp:1 . $ docker run --rm -d -p 8080:80 webapp:1
ローカルのテストではCloudFrontのURLを参照して画像を表示できています。

AWS FargateでWEBアプリ起動
AWS CopilotでサクッとFargateにデプロイします。

Dockerfileが完成していればコマンドを実行して見守るだけです。途中でイメージのタグ名を入力を求められますので完全放置だとデプロイまで進まないので見守ってください。
copilot init --app webapp \ --name api \ --type 'Load Balanced Web Service' \ --dockerfile './Dockerfile' \ --port 80 \ --deploy --- 省略 --- Note: Couldn't find a git commit sha to use as an image tag. Are you in a git repository? Input an image tag value: latest --- 省略 --- ✔ Deployed api, you can access it at http://webap-Publi-8Q600A2ZWH6U-164835002.us-east-1.elb.amazonaws.com.
CopilotでFargateへデプロイが完了しました。デプロイ後以下の作業を実施。
- ALBはHTTPのリスナールールしか用意されていないため、HTTPSのリスナールール追加
 - ALBに証明書を設定
 - Route53で独自ドメインを設定(登録済みの
www.サブドメインをCopilotで作成されたALBへ変更) 
WEBアクセステスト
コンテナ化した際は画像ファイルのパスをCloudFrontのパスへ変更すれば画像を配信し表示できることを確認できました。

Copilotでお片付け
簡単にFargateで検証した環境を削除できます、検証するときは楽です。
$ copilot app delete
今回はRoute53で独自ドメインを手動で設定しているので手動の箇所は手動でお片付けしないといけません。
おわりに
WEBアプリケーションを作る側の人間ではないのでぼやっととした認識だったため、一度試してみたかったのでやってみました。しかし、実際のWEBシステムはもっと複雑なことでしょう。そのため考慮する点は多いのでしょうけど画像だけの検証する分には修正箇所は軽微でした。
網走の流氷砕氷船おーろらの画像は網走市公式サイトより、網走のPRや紹介にお役立てくださいとのことでお借りしました。画像ファイルのスペルがororaとなっており、これが正しい表記なのかと気になります。
正解はauroraでした。ゆるい地方感があり味わい深くいいですね。






