話題の記事

WEBサーバをコンテナにした際に静的コンテンツはどこへ保存するのか

2021.02.20

EC2で起動しているWEBサーバをコンテナ化する場合、静的コンテンツはどこへ保存するのか。 EC2内に静的コンテンツを持っていたならS3へ静的なコンテンツは移動し、CloudFront経由で配信するのがよくあるパターンでしょう。静的コンテンツをWEBサーバ以外から配信する際にWEBアプリケーションではどのような修正が必要になるのか。ぼやっとした理解なので画像の配信を例に検証してみました。

Icons made by Freepik from www.flaticon.com

検証環境

下記の構成で実際に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

main.go

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/[ファイル名]になります。

index.html

{{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で画像配信

Icons made by Freepik from www.flaticon.com

必要最低限のファイルを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設定

Icons made by Freepik from www.flaticon.com

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にデプロイします。

Icons made by Freepik from www.flaticon.com

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でした。ゆるい地方感があり味わい深くいいですね。

参考