前提条件/構成条件
- APIサーバーはNginx + Golang
 - ユーザー情報はCognitoに格納されている
 - ユーザー認証をAPIGatewayのAuthorizerで行なう
 - CloudFront → API Gateway → Fargate
 
※AWSの設定作業はコンソールより手作業で行ないます
infra as codeはありません
構成図
コンテナの準備
以下のDockerfileを準備します。
配置場所は当記事では{ルートdir}/release/app/および{ルートdir}/release/nginx/配下としています。
まずはGoで作成するアプリケーションです。
FROM golang:1.13.0-alpine as builder
ENV ROOT_PATH /go/src/github.com/xxx/yyy
WORKDIR $ROOT_PATH
RUN apk add --no-cache alpine-sdk git
RUN addgroup -g 10001 -S admin \
    && adduser -u 10001 -G admin -S admin
# modules
COPY go.mod go.sum ./
RUN go mod download
# application
COPY . .
# build
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o /go/bin/app && \
    chmod 755 /go/bin/app && \
    chown admin:admin /go/bin/app
FROM scratch
COPY --from=builder /etc/group /etc/group
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /go/bin/app /go/bin/app
USER admin
EXPOSE 9000
CMD ["/go/bin/app"]
アプリケーションはとりあえず以下な感じでよいでしょう。
go.modとgo.sumも配置しておいてください。
package main
import (
	"github.com/labstack/echo"
	"net/http"
)
func main() {
	e := echo.New()
	e.GET("/", func(c echo.Context) error {
		return c.String(http.StatusOK, "Hello World!")
	})
	e.Logger.Fatal(e.Start(":9000"))
}
続いてNginxです。
FROM nginx:1.17.4-alpine
ADD ./release/nginx/nginx.conf /etc/nginx/conf.d/default.conf
Fargateは127.0.0.1で待ち受けます。
PortはGo側で設定したもの(9000)を設定します。
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    include /etc/nginx/default.d/*.conf;
    location / {
        proxy_pass http://127.0.0.1:9000;
    }
}
AWS構築手順
VPC
VPCの作成
特に言及することはありません。
既に利用されているものがあればそのまま使いましょう
サブネットの作成
プライベートサブネットで作成します。
Fargateの場合はサーバーに固定IPを割り振りませんので、NATゲートウェイを設定しておきます。
当記事では以下の2つのCIDRブロックとしています。
- 10.1.12.0/24(ap-northeast-1a)
 - 10.1.52.0/24(ap-northeast-1c)
 
後述するNATゲートウェイも作成する場合は、パブリックサブネットも一緒に作成しておきます。
NATゲートウェイ
NATゲートウェイを作成します。ElasticIPが必要になりますので新しく作成しましょう。
 ※既に利用されているものがあればそのまま使いましょう
作成後は、先ほど作成したサブネットのルートテーブルに紐付けるのを忘れないようにしましょう。
※NATゲートウェイ自体が配置されているパブリックサブネットはインターネットゲートウェイが必要になります。
 忘れないように設定しておきましょう。
セキュリティグループ作成
続いてセキュリティグループを作成します。
このセキュリティグループはNLBからコンテナ(Nginx)へのアクセスで用いられます。
NLBについては後述します。以下のように先ほど作成したサブネットのCIDRブロックを記述して反映します。
ロードバランサー
NLBの作成
APIGatewayからロードバランサーへルーティングする場合、VPCリンクで繋ぐ必要があります。
当記事ではAPIをVPCのプライベート空間(インターネットに公開しない)に配置するためNLBによる構築となります。
スキームは内部、リスナーはTLS(セキュアTCP)を選択します。
VPCは先ほど作成したVPCとサブネットを指定します。
証明書
ACMに既に証明書が存在する場合はそのまま使いましょう。
当記事ではセキュリティポリシーをELBSecurityPolicy-TLS-1-2-2017-01を選択しています。
ターゲットグループの作成
新しくターゲットグループを作成します。
ターゲットの種類はIPを選択します
次画面のターゲットの登録は、一旦スキップでOKです。
ドメインの確保
NLBの作成が完了したらDNSを自身のドメインに設定します。
Route53を利用していればAliasレコードで登録できるかと思います。
当記事ではホスティングゾーンが別のためCNAMEで設定します。
 ※このドメインはサービスのエンドポイントではありません。
  CloudFront→①→APIGateway→②→NLB の②にあたる部分です。
  ①については後ほど設定します。
例: elb-origin.domain.com など
ECR
リポジトリの作成
ECRにリポジトリを作成しておきます。
当記事ではリポジトリは以下の名称にしています。
- {サービス名}/app
 - {サービス名}/nginx
 
これでコンテナのURIが以下のようになると思います。
- {AWSのアカウントID}.dkr.ecr.ap-northeast-1.amazonaws.com/{サービス名}/app
 - {AWSのアカウントID}.dkr.ecr.ap-northeast-1.amazonaws.com/{サービス名}/nginx
 
コンテナのプッシュ
当記事の初めに準備したコンテナをプッシュします。
 ※CI環境では自動プッシュ/デプロイを設定することになります。今回は最初だけ手動プッシュとします。
まずはビルド
$ docker build --no-cache -t {appコンテナ名}:latest -f release/app/Dockerfile .
$ docker build --no-cache -t {nginxコンテナ名}:latest -f release/nginx/Dockerfile .
タグ付け
$ docker tag {appコンテナ名}:latest {ECRのコンテナURI}:latest
$ docker tag {nginxコンテナ名}:latest {ECRのコンテナURI}:latest
ECRにログインします
 ※awscliが使えない場合はインストールしてください
$ $(aws ecr get-login --no-include-email)
プッシュします
$ docker push {ECRのコンテナURI(app)}:latest
$ docker push {ECRのコンテナURI(nginx)}:latest
ECS
クラスターの作成
まずはクラスターを作成します。クラスターは単なる名前空間になります。
コンテナの設定、配置する数やスペックについては後述するサービス、タスクによる定義で設定します。
クラスターテンプレートはネットワーキングのみを指定して作成します。
IAMロールの作成
次にIAMロールを作成します。2つ作成します
タスクロール
このロールはコンテナ内部でAWSへのAPIリクエストを行なうためにタスクで使用するものです。
例えばS3にアクセスが必要なアプリケーションの場合、S3へのアクセスが可能なロールを定義します。
コンソールから作成する場合は、以下の項目を信頼されたエンティティとして作成します。
Elastic Container Service Task
タスク実行ロール
このロールはタスクのプルやログの発行などを管理するロールです。
AmazonECSTaskExecutionRolePolicyというポリシーを付与して作成します。
タスク定義の作成
コンソールから設定する場合、まずタスクロールとタスク実行ロールを指定します。
先ほど作成したIAMロールを指定します。
タスク実行ロールにはAmazonECSTaskExecutionRolePolicyがアタッチされているロールを指定します。
タスクサイズはとりあえず最低スペック(メモリ0.5GB、CPUが0.25vCPU)にしておきます。
続いてコンテナの追加ですが、ECRにプッシュしてあるappコンテナとnginxコンテナを指定します。
ヘルスチェックやログ出力などの細かい設定はとりあえずデフォルトのまま進めます。
nginxコンテナは、ポートマッピングを設定します。80を設定してください。
appコンテナとnginxコンテナを設定できればタスク定義の作成を完了します。
サービスの作成
サービスはクラスターの中から作成することができます。
ステップ1 サービスの設定
ステップ1のサービスの設定については特に難しくはありません。
起動タイプをFARGATEとし、タスク定義やクラスターは先ほど作成したものを選択します。
タスクの数は最低料金に抑えるため1にしておきます。(商用では適切なタスク数を検討してください)
デプロイメントについては、当記事ではローリングアップデートで進めます。
ステップ2 ネットワーク構成
ステップ2のネットワーク構成です。
VPCとセキュリティグループは、先ほど作成したVPCとサブネット、セキュリティグループを選択します。
当記事では以下のサブネットを作成していました。
- 10.1.12.0/24
 - 10.1.52.0/24
 
パブリックIPの自動割り当てについてはDISABLEDにしておきます。
続いて、ロードバランサーの設定です。先ほど作成したNLBを指定します。
ターゲットグループ名も、先ほど作成しているので選択します。
サービスの検出(オプション)という項目については当記事ではOFFで設定します。
ステップ3 Auto Scaling(オプション)
ステップ3はAutoScaleingの設定です。
商用で利用する場合は必ず設定しましょう。
当記事では設定せずに先に進みます。
API Gateway
VPCリンクの作成
APIGatewayよりVPCリンクを作成します。
ターゲットNLBを先ほど作成したNLBとします。
APIGatewayの作成
APIGatewayを作成します。
メソッドの作成
一旦、/パスにGETメソッドを定義します。
統合タイプはVPCリンクを選択し、先ほど作成したVPCリンクを設定します。
エンドポイントURLは、先ほど作成したNLBのAliasレコード(もしくはCNAME)を設定します。
ここはNLBでインポートしたACMに対応するドメインである必要があります。
APIのデプロイ
メソッドが作成したらデプロイを行ないます。(コンソールから作業するとよく忘れます。。)
ステージ名は適当にdevとでもしておきます。
CloudFront
Create Distribution
CloudFrontを設定していきます。
各項目については以下の通りです。これ以外の項目については要件により適宜設定してください。
| 項目 | 内容 | 
|---|---|
| Origin Domain Name | 
先ほど作成したAPIGatewayのドメイン 例: xxxxx.execute-api.ap-northeast-1.amazonaws.com (ステージ名は除く) | 
| Origin Path | 
APIGatewayのステージ名 例: /dev
 | 
| Origin Protocol Policy | HTTPS Only | 
| Viewer Protocol Policy | Redirect HTTP to HTTPS | 
| Allowed HTTP Methods | GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE | 
| Alternate Domain Names(CNAMEs) | 自身のサービス用ドメイン(APIのエンドポイント) | 
| SSL Certificate | Custom SSL Certificate (example.com): (自身のACM証明書を選択) | 
ここまで出来れば、自身のドメインでアクセスすることでHello World!が表示されるはずです。
Authorizerの設定
Cognito
前提条件として、Cognitoにエンドユーザー(当APIにアクセスするユーザー)の情報が格納されていることとします。
Cognitoの扱い方については当記事では対象外となります。
APIGateway
新しいオーソライザーの作成
自身のAPIGatewayのメニューより、オーソライザー>新しいオーソライザーの作成を選択します。
タイプをCognitoとし、ユーザープール名を指定します。
トークンのソースはAuthorizationとします。
オーソライザーを作成したら、新しくPOSTのAPIおよびメソッドを作成します。
このAPIではCognitoより、認証されたユーザーの情報を受け取る検証をするためPOSTとしています。
オーソライザーは以下のメソッドリクエストの認可より設定します。
次に統合リクエストのマッピングテンプレートを選択します。
以下の形式でCognitoからのパラメータ群を取得することができます。
{
  "sub": "$context.authorizer.claims.sub",
  "username": "$context.authorizer.claims['cognito:username']",
  "birthdate": "$context.authorizer.claims.birthdate",
  "gender": "$context.authorizer.claims.gender",
  ...(省略)
}
アプリケーション側(Go)では以下のように取得できるかと思います。
package main
import (
	"github.com/labstack/echo"
	"net/http"
)
type User struct {
	Sub string `json:"sub"`
	Username string `json:"username"`
	BirthDate string `json:"birthdate"`
	Gender string `json:"gender"`
	Name string `json:"name"`
	Locale string `json:"locale"`
	Email string `json:"email"`
	Picture string `json:"picture"`
}
func main() {
	e := echo.New()
	// ユーザーの取得
	e.POST("/test", func(c echo.Context) error {
		user := new(User)
		if err := c.Bind(user); err != nil {
			return err
		}
		return c.JSON(http.StatusOK, user)
	})
	e.Logger.Fatal(e.Start(":9000"))
}
CI構築
当記事ではCI環境をCodePipelineで行ないます。
CodeBuild
ビルドプロジェクトの作成
まずはCodeBuildを設定します。
送信元については、当記事ではGithubを設定しています。
ビルド環境についてはAmazon Linux 2を選択します。(好きなものでいいです)
Buildspacについてはbuildspec.yamlを配置する予定のパスを指定します。
buildspec.yamlの配置
内容は以下の通りです。
version: 0.2
phases:
  install:
    runtime-versions:
      docker: 18
    commands:
  pre_build:
    commands:
      - $(aws ecr get-login --no-include-email)
  build:
    commands:
      - docker build --no-cache -t {appコンテナ名}:latest -f release/app/Dockerfile .
      - docker build --no-cache -t {nginxコンテナ名}:latest -f release/nginx/Dockerfile .
      - docker tag {appコンテナ名}:latest {appのコンテナURI}:latest
      - docker tag {nginxコンテナ名}:latest {nginxのコンテナURI}:latest
  post_build:
    commands:
      - docker push {appのコンテナURI}:latest
      - docker push {nginxのコンテナURI}:latest
      - |
        printf '[{"name":"app","imageUri":"%s"},{"name":"nginx","imageUri":"%s"}]' \
        {appのコンテナURI}:latest {nginxのコンテナURI}:latest > imagedefinitions.json
CodeDeploy
※CodeDeployについてはBlue/Greenデプロイを利用する場合のみ設定します。
 当記事ではローリングアップデートで進めるためこちらの作成は不要です。
CodePipeline
新規のパイプラインを作成する
続いてCodePipelineを作成します。
ソースステージについては、当記事ではGithubを選択しています。
ビルドステージについては先ほど作成したプロジェクトを指定します。
デプロイステージについてはAmazon ECSを指定します。
Blue/Greenデプロイの場合は別途Amazon ECS(ブルー/グリーン)を指定します。
イメージ定義ファイルはimagedefinitions.jsonです。CodeBuildのbuildspec.yamlで出力するJSONです。
APIGatewayのデプロイ
APIGatewayについても、今後手動で設定していくのが辛いのでCI環境内でデプロイしたいものです。
今回は、CodeBuild上でデプロイします。
buildspec.yamlのpost_build(ビルド完了後)に以下のコマンドを追記します
CodeBuiildの実行ロールにAPIGatewayの実行権限を付与するのを忘れないでください。
# API Gateway Deploy
- aws apigateway put-rest-api --rest-api-id {APIGatewayのID} --mode overwrite --body file://openapi.yaml
- aws apigateway create-deployment --rest-api-id {APIGatewayのID} --stage-name {APIGatewayのステージ名}
ここでopenapi.yamlというファイルが登場しています。
こちらは、APIGatewayの構成を記述したOpenAPI(もしくはSwagger)形式のyamlとなります。
APIGatewayのステージ>エクスポートよりエクスポートができ、追記したものをインポートしデプロイすることができます。
アプリケーション側でAPI、メソッド等を追記したらこのファイルも更新しましょう。
openapi: "3.0.1"
info:
  title: "{Your API Title}"
  version: "xxx"
servers:
- url: "https://example.execute-api.ap-northeast-1.amazonaws.com/{basePath}"
  variables:
    basePath:
      default: "/dev"
paths:
  /:
    get:
      responses:
        200:
          description: "200 response"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Empty"
      x-amazon-apigateway-integration:
        uri: "https://elb-origin.domain.com/"
        responses:
          default:
            statusCode: "200"
        passthroughBehavior: "when_no_match"
        connectionType: "VPC_LINK"
        connectionId: "xxx"
        httpMethod: "GET"
        type: "http"
components:
  schemas:
    Empty:
      title: "Empty Schema"
      type: "object"
以上で全行程が完了です。



























