概要
AWS Copilotを使い、AWS ECS x Fargateへアプリケーションをデプロイしてみます。
AWS Copilotとは?
- 「Copilot」を和訳すると「副操縦士」
- AWSが提供するECS CLIの後継
- 従来のECS CLIより簡単にFargateへのコンテナデプロイを実現できる
- 詳しくはAWSのブログを参照
- AWS Copilotのソースコードはgithubに公開されている
簡単に言うと、**「ちゃんと動くDockerfile」があれば、$ copilot init
とか $ copilot deploy
といった簡単なコマンドだけでECSにアプリケーションをデプロイできるよ!**というツールです。
モチベーション
- 従来であれば、こうしたECS環境を使った公開アプリケーションを開発しようとしたとき、ある程度のネットワーク構成なども同時に構築する必要があり、手間がかかるもの (ポート設定、ロードバランサー、サービスディスカバリ etc...)
- Copilotがイケてると思ったのはそのあたりの低レイヤの構築をいい感じにやってくれる、という点 (その恩恵としてアプリケーションエンジニアはより開発に集中できる)
- PaaSに近い感覚で、さっと小さなコンテナベースのアプリケーションを公開するのに使えそう、と思い基本的な使い方を学習しておこうと
環境概要
- Mac OS Catalina
- Dockerインストール済
- AWS CLI v2インストール済
なので、ここではCopilotのインストールから実行していきます。
AWS Copilotのインストール
インストール方法はAWS Copilotのドキュメントに記載されています。
https://github.com/aws/copilot-cli/wiki
$ brew install aws/tap/copilot-cli
サンプルアプリケーションの作成
Dockerfileで構築可能なアプリケーションであればなんでも良いのですが、
ここではタイトルの通り、Kotlinで単純なSpringBootアプリケーションを作成し、それをECS上に構築してみます。
利用したJavaとGradleのバージョンは以下。
(ちなみに、jenvとsdkmanで入れたもの)
$ java --version
openjdk 13.0.1 2019-10-15
OpenJDK Runtime Environment (build 13.0.1+9)
OpenJDK 64-Bit Server VM (build 13.0.1+9, mixed mode, sharing)
$ gradle -v
------------------------------------------------------------
Gradle 6.5
------------------------------------------------------------
...
ディレクトリ構造
最終的にこんな感じになります。
$ tree aws-copilot-app-example/
aws-copilot-app-example/
├── Dockerfile
├── README.md
├── build.gradle.kts
├── copilot
│ └── aws-copilot-app-example-service
│ └── manifest.yml
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── src
├── main
│ ├── kotlin
│ │ └── com
│ │ └── example
│ │ ├── ExampleApplication.kt
│ │ └── ExampleController.kt
│ └── resources
│ └── application.yml
└── test
├── kotlin
│ └── com
│ └── example
│ └── ExampleControllerTest.kt
└── resources
15 directories, 14 files
SpringBootプロジェクト作成
Spring Initializrを使ってもいいですが、自分の場合$ gradle init
で作りました。
$ gradle init
Select type of project to generate:
1: basic
2: application
3: library
4: Gradle plugin
Enter selection (default: basic) [1..4] 2
Select implementation language:
1: C++
2: Groovy
3: Java
4: Kotlin
5: Swift
Enter selection (default: Java) [1..5] 4
Select build script DSL:
1: Groovy
2: Kotlin
Enter selection (default: Kotlin) [1..2] 2
Project name (default: foo): aws-copilot-app-example
Source package (default: aws.copilot.app.example): com.example
BUILD SUCCESSFUL in 33s
2 actionable tasks: 2 executed
その後、各種ファイルを作成・修正していきます。
build.gradle.kts
ビルド設定です。
こんな感じ。
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "2.3.2.RELEASE"
id("io.spring.dependency-management") version "1.0.8.RELEASE"
kotlin("jvm") version "1.3.72"
kotlin("plugin.spring") version "1.3.72"
}
group = "com.example"
version = "0.0.1-SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// SpringBoot
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.11.2")
// Testing
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
}
testImplementation("io.mockk:mockk:1.10.0")
}
tasks.withType<Test> {
useJUnitPlatform()
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "1.8"
}
}
ExampleApplication.kt
SpringBootアプリケーションの起動部です。
package com.example
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class ExampleApplication
fun main(args: Array<String>) {
runApplication<ExampleApplication>(*args)
}
ExampleController.kt
Hello AWS Copilot!!!
というメッセージを返す GET
のAPIを作成します。
package com.example
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
@RestController
class ExampleController {
@GetMapping("/")
fun get(): ResponseEntity<Result> =
ResponseEntity(Result(message = "Hello AWS Copilot!!!"), HttpStatus.OK)
data class Result(
val message: String
)
}
ExampleControllerTest.kt
こちらはコントローラのテストコードです。
package com.example
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.junit.jupiter.SpringExtension
@ExtendWith(SpringExtension::class)
@SpringBootTest
@AutoConfigureMockMvc
class ExampleControllerTest {
@Autowired
private lateinit var mockMvc: MockMvc
@Autowired
private val mapper = jacksonObjectMapper()
@Test
fun `response data contains welcome message`() {
val expected = ExampleController.Result(
message = "Hello AWS Copilot!!!"
)
mockMvc.perform(MockMvcRequestBuilders.get("/"))
.andExpect(status().isOk)
.andExpect(content().json(mapper.writeValueAsString(expected)))
}
}
Dockerfileの作成
上記で作成したSpringBootアプリケーションをコンテナ化するために、以下のようなDockerfileを作成します。
FROM openjdk:jdk-alpine
VOLUME /tmp
RUN mkdir /aws-copilot-app-example
WORKDIR /aws-copilot-app-example
ENV JAVA_OPTS=""
ENV APP_VERSION=0.0.1-SNAPSHOT
COPY ./build/libs/aws-copilot-app-example-$APP_VERSION.jar /aws-copilot-app-example
EXPOSE 8080
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar ./build/libs/aws-copilot-app-example-$APP_VERSION.jar" ]
試しにローカルで起動してみます。
# アプリケーションのビルド(jarを生成)
$ gradle clean build
# Dockerイメージのビルド
$ docker build -t aws-copilot-app-example:latest ./
# Dockerイメージを起動
$ docker run --rm -it -p 80:8080 --name aws-copilot-app-example aws-copilot-app-example:latest
動作確認
$ curl http://localhost
{"message":"Hello AWS Copilot!!!"}
成功すると上記のようにメッセージが返ってきます。
AWS Copilotを使ってECSに上記のSpringBootアプリケーションをデプロイ
Dockerfileがあるディレクトリで以下のコマンドを実行します。
$ copilot init
すると、作成したいアプリケーションについて順に聞かれるので、対話式に入力していきます。
ここでは以下のように入力しました。
項目 | 入力内容 |
---|---|
Application name | aws-copilot-app-example |
Service type | Load Balanced Web Service |
Service name | aws-copilot-app-example-service |
Dockerfile | ./Dockerfile |
動作確認を簡単化するため、公開サービスを想定したLoad Balanced Web Service
として立ち上げていますが、Backend Service
も試してみたところ、Cloud Map上にネームスペースもきちんと作成されていました(サービスディスカバリもバッチリ。しゅごい)
その後、以下のようにtest環境にデプロイしてよいか聞かれるので「y」と入力しEnter
Would you like to deploy a test environment? [? for help] (y/N) y
諸々回答していくと、出力は以下のようになります。
$ copilot init
Note: It's best to run this command in the root of your Git repository.
Welcome to the Copilot CLI! We're going to walk you through some questions
to help you get set up with an application on ECS. An application is a collection of
containerized services that operate together.
Application name: aws-copilot-app-example
Service name: aws-copilot-app-example-service
Dockerfile: ./Dockerfile
Ok great, we'll set up a Load Balanced Web Service named aws-copilot-app-example-service in application aws-copilot-app-example listening on port 8080.
✔ Created the infrastructure to manage services under application aws-copilot-app-example.
✔ Manifest file for service aws-copilot-app-example-service already exists at copilot/aws-copilot-app-example-service/manifest.yml, skipping writing it.
Your manifest contains configurations like your container size and port (:8080).
✔ Created ECR repositories for service aws-copilot-app-example-service.
All right, you're all set for local development.
Deploy: Yes
⠹ Proposing infrastructure changes for the test environment.
さすがに環境をまるっと一式作るのでけっこう時間がかかる。しばらく待つ。
ちゃんと処理が進んでいるのか気になるようであれば、AWSコンソールよりCloudFormationのイベントなどを参照すると進捗状況を確認できる。
✔ Deployed aws-copilot-app-example-service, you can access it at http://aws-c-Publi-xxx-xxx.ap-northeast-1.elb.amazonaws.com.
AWSにデプロイしたアプリケーションの動作確認
$ curl http://aws-c-Publi-xxx-xxx.ap-northeast-1.elb.amazonaws.com
{"message":"Hello AWS Copilot!!!"}
すげぇ、マジでできてしまった(当たり前ですが)
実質、$ copilot init
しか打ってないんだがw (神なの)
特に面白いと思ったのは、Dockerfile
上でEXPOSE 8080
と指定し、コンテナの8080ポートを公開しているのですが、
これを解釈してELBの80ポートからコンテナの8080ポートまでのルートをマッピングしてくれているところです。
これは本当に気が利いているw
作成したサービスの削除
放っておくとお金もかかるので、作成したサービスを削除します。
$ copilot app ls
aws-copilot-app-example
$ copilot app delete aws-copilot-app-example
AWSのprofileが複数ある場合、「この環境ではどのプロファイルを使って消します?」と聞かれるので、自身の環境に合わせて削除用のprofileを選ぶ。(ここではdefault
を選択)
Which named profile should we use to delete test? default
今回作成したサンプルコード
※執筆時点から変更される可能性もあります
https://github.com/otajisan/aws-copilot-app-example