├── .circleci └── config.yml ├── handson ├── handson │ ├── .gitignore │ └── main.tf ├── laravel │ ├── .gitignore │ ├── acm │ │ └── main.tf │ ├── ecs_cluster │ │ └── main.tf │ ├── ecs_laravel │ │ ├── container_definitions.json │ │ └── main.tf │ ├── ecs_nginx │ │ ├── container_definitions.json │ │ └── main.tf │ ├── elb │ │ └── main.tf │ ├── main.tf │ ├── network │ │ └── main.tf │ └── rds │ │ └── main.tf ├── syntax │ ├── .gitignore │ ├── acm │ │ └── main.tf │ ├── container_definitions.json │ ├── ecs_cluster │ │ └── main.tf │ ├── elb │ │ └── main.tf │ ├── main.tf │ ├── network │ │ └── main.tf │ └── nginx │ │ └── main.tf └── vpc-handson │ ├── .gitignore │ └── main.tf └── mkdocs ├── docker-compose.yaml ├── docs ├── assets │ ├── custom.css │ └── custom.js ├── first │ ├── about.md │ ├── handson.md │ ├── imgs │ │ ├── aws.png │ │ ├── describe-vpcs.png │ │ ├── handson-vpc.png │ │ ├── iac-logos.png │ │ ├── iam-accesskey.png │ │ ├── iam-createconfirm.png │ │ ├── iam-inputeusername.png │ │ ├── iam-policyattach.png │ │ ├── iam-search.png │ │ ├── iam-tag.png │ │ ├── iam-top.png │ │ ├── iam-usercreate.png │ │ ├── logo.png │ │ └── tf-docker.png │ └── preparation.md ├── handson │ ├── about.md │ ├── alb.md │ ├── ecs.md │ ├── https.md │ ├── imgs │ │ ├── alb-dns.png │ │ ├── alb-ecs.png │ │ ├── alb-empty-listener.png │ │ ├── alb-fixed-response.png │ │ ├── alb-http-listener.png │ │ ├── alb-http-pong.png │ │ ├── aws.png │ │ ├── ecs-alb.png │ │ ├── ecs-cluster.png │ │ ├── ecs-control-data.png │ │ ├── ecs-fargate.png │ │ ├── ecs-nginx-design.png │ │ ├── ecs-svc-create.png │ │ ├── ecs-svc.png │ │ ├── ecs-td.png │ │ ├── ecs.png │ │ ├── https.png │ │ ├── network-igw.png │ │ ├── network-natgw.png │ │ ├── network-routes-private.png │ │ ├── network-routes-public.png │ │ ├── network-routes.png │ │ ├── network-rtb.png │ │ ├── network-subnet.png │ │ ├── network-vpc.png │ │ ├── network.png │ │ ├── rtb-list.png │ │ ├── subnet-list.png │ │ └── vpc-list.png │ ├── syntax.md │ └── vpc.md ├── imgs │ └── aws.png ├── index.md ├── laravel │ ├── about.md │ ├── cleanup.md │ ├── docker-compose.md │ ├── ecr.md │ ├── ecs.md │ ├── imgs │ │ ├── architecture.png │ │ ├── aurora.png │ │ ├── aws-laravel-migrate.png │ │ ├── aws-migration-cloudwatch.png │ │ ├── aws-migration-result.png │ │ ├── aws-run-task.png │ │ ├── aws-select-task.png │ │ └── ecs-laravel.png │ └── rds.md └── next-step.md └── mkdocs.yml /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | jobs: 3 | publish: 4 | docker: 5 | - image: squidfunk/mkdocs-material 6 | steps: 7 | - add_ssh_keys: 8 | fingerprints: 9 | - "56:f7:e7:2c:33:6b:8b:b5:0b:4a:7a:71:3e:f5:72:63" 10 | - checkout 11 | - run: 12 | name: Build 13 | command: | 14 | mv .git /tmp/ 15 | cd mkdocs 16 | mkdocs build 17 | cp -r site/* /tmp/ 18 | - run: 19 | name: Publish 20 | command: | 21 | cd /tmp/ 22 | apk add --no-cache git openssh-client 23 | git config user.email "circleci@circle.com" 24 | git config user.name "circleci" 25 | git checkout --orphan gh-pages 26 | git add . && git commit -m "[ci skip] Publish" 27 | git push --force origin gh-pages 28 | 29 | workflows: 30 | version: 2 31 | publish: 32 | jobs: 33 | - publish: 34 | filters: 35 | branches: 36 | only: master 37 | -------------------------------------------------------------------------------- /handson/handson/.gitignore: -------------------------------------------------------------------------------- 1 | .terraform 2 | terraform.tfstate 3 | terraform.tfstate.backup 4 | -------------------------------------------------------------------------------- /handson/handson/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "ap-northeast-1" 3 | } 4 | 5 | # VPC 6 | # https://www.terraform.io/docs/providers/aws/r/vpc.html 7 | resource "aws_vpc" "main" { 8 | cidr_block = "10.0.0.0/16" 9 | 10 | tags = { 11 | Name = "handson" 12 | } 13 | } 14 | 15 | # Subnet 16 | # https://www.terraform.io/docs/providers/aws/r/subnet.html 17 | resource "aws_subnet" "public_1a" { 18 | vpc_id = "${aws_vpc.main.id}" 19 | 20 | availability_zone = "ap-northeast-1a" 21 | cidr_block = "10.0.1.0/24" 22 | 23 | tags = { 24 | Name = "handson-public-1a" 25 | } 26 | } 27 | 28 | resource "aws_subnet" "public_1c" { 29 | vpc_id = "${aws_vpc.main.id}" 30 | 31 | availability_zone = "ap-northeast-1c" 32 | cidr_block = "10.0.2.0/24" 33 | 34 | tags = { 35 | Name = "handson-public-1c" 36 | } 37 | } 38 | 39 | resource "aws_subnet" "public_1d" { 40 | vpc_id = "${aws_vpc.main.id}" 41 | 42 | availability_zone = "ap-northeast-1d" 43 | cidr_block = "10.0.3.0/24" 44 | 45 | tags = { 46 | Name = "handson-public-1d" 47 | } 48 | } 49 | 50 | resource "aws_subnet" "private_1a" { 51 | vpc_id = "${aws_vpc.main.id}" 52 | 53 | availability_zone = "ap-northeast-1a" 54 | cidr_block = "10.0.10.0/24" 55 | 56 | tags = { 57 | Name = "handson-public-1a" 58 | } 59 | } 60 | 61 | resource "aws_subnet" "private_1c" { 62 | vpc_id = "${aws_vpc.main.id}" 63 | 64 | availability_zone = "ap-northeast-1c" 65 | cidr_block = "10.0.20.0/24" 66 | 67 | tags = { 68 | Name = "handson-private-1c" 69 | } 70 | } 71 | 72 | resource "aws_subnet" "private_1d" { 73 | vpc_id = "${aws_vpc.main.id}" 74 | 75 | availability_zone = "ap-northeast-1d" 76 | cidr_block = "10.0.30.0/24" 77 | 78 | tags = { 79 | Name = "handson-private-1d" 80 | } 81 | } 82 | 83 | # Internet Gateway 84 | # https://www.terraform.io/docs/providers/aws/r/internet_gateway.html 85 | resource "aws_internet_gateway" "main" { 86 | vpc_id = "${aws_vpc.main.id}" 87 | 88 | tags = { 89 | Name = "handson" 90 | } 91 | } 92 | 93 | # Elasti IP 94 | # https://www.terraform.io/docs/providers/aws/r/eip.html 95 | resource "aws_eip" "nat_1a" { 96 | vpc = true 97 | 98 | tags = { 99 | Name = "handson-natgw-1a" 100 | } 101 | } 102 | 103 | # NAT Gateway 104 | # https://www.terraform.io/docs/providers/aws/r/nat_gateway.html 105 | resource "aws_nat_gateway" "nat_1a" { 106 | subnet_id = "${aws_subnet.public_1a.id}" # NAT Gatewayを配置するSubnetを指定 107 | allocation_id = "${aws_eip.nat_1a.id}" # 紐付けるElasti IP 108 | 109 | tags = { 110 | Name = "handson-1a" 111 | } 112 | } 113 | 114 | resource "aws_eip" "nat_1c" { 115 | vpc = true 116 | 117 | tags = { 118 | Name = "handson-natgw-1c" 119 | } 120 | } 121 | 122 | resource "aws_nat_gateway" "nat_1c" { 123 | subnet_id = "${aws_subnet.public_1c.id}" 124 | allocation_id = "${aws_eip.nat_1c.id}" 125 | 126 | tags = { 127 | Name = "handson-1c" 128 | } 129 | } 130 | 131 | resource "aws_eip" "nat_1d" { 132 | vpc = true 133 | 134 | tags = { 135 | Name = "handson-natgw-1d" 136 | } 137 | } 138 | 139 | resource "aws_nat_gateway" "nat_1d" { 140 | subnet_id = "${aws_subnet.public_1d.id}" 141 | allocation_id = "${aws_eip.nat_1d.id}" 142 | 143 | tags = { 144 | Name = "handson-1d" 145 | } 146 | } 147 | 148 | # Route Table 149 | # https://www.terraform.io/docs/providers/aws/r/route_table.html 150 | resource "aws_route_table" "public" { 151 | vpc_id = "${aws_vpc.main.id}" 152 | 153 | tags = { 154 | Name = "handson-public" 155 | } 156 | } 157 | 158 | # Route 159 | # https://www.terraform.io/docs/providers/aws/r/route.html 160 | resource "aws_route" "public" { 161 | destination_cidr_block = "0.0.0.0/0" 162 | route_table_id = "${aws_route_table.public.id}" 163 | gateway_id = "${aws_internet_gateway.main.id}" 164 | } 165 | 166 | # Association 167 | # https://www.terraform.io/docs/providers/aws/r/route_table_association.html 168 | resource "aws_route_table_association" "public_1a" { 169 | subnet_id = "${aws_subnet.public_1a.id}" 170 | route_table_id = "${aws_route_table.public.id}" 171 | } 172 | 173 | resource "aws_route_table_association" "public_1c" { 174 | subnet_id = "${aws_subnet.public_1c.id}" 175 | route_table_id = "${aws_route_table.public.id}" 176 | } 177 | 178 | resource "aws_route_table_association" "public_1d" { 179 | subnet_id = "${aws_subnet.public_1d.id}" 180 | route_table_id = "${aws_route_table.public.id}" 181 | } 182 | 183 | # Route Table (Private) 184 | # https://www.terraform.io/docs/providers/aws/r/route_table.html 185 | resource "aws_route_table" "private_1a" { 186 | vpc_id = "${aws_vpc.main.id}" 187 | 188 | tags = { 189 | Name = "handson-private-1a" 190 | } 191 | } 192 | 193 | resource "aws_route_table" "private_1c" { 194 | vpc_id = "${aws_vpc.main.id}" 195 | 196 | tags = { 197 | Name = "handson-private-1c" 198 | } 199 | } 200 | 201 | resource "aws_route_table" "private_1d" { 202 | vpc_id = "${aws_vpc.main.id}" 203 | 204 | tags = { 205 | Name = "handson-private-1d" 206 | } 207 | } 208 | 209 | # Route (Private) 210 | # https://www.terraform.io/docs/providers/aws/r/route.html 211 | resource "aws_route" "private_1a" { 212 | destination_cidr_block = "0.0.0.0/0" 213 | route_table_id = "${aws_route_table.private_1a.id}" 214 | nat_gateway_id = "${aws_nat_gateway.nat_1a.id}" 215 | } 216 | 217 | resource "aws_route" "private_1c" { 218 | destination_cidr_block = "0.0.0.0/0" 219 | route_table_id = "${aws_route_table.private_1c.id}" 220 | nat_gateway_id = "${aws_nat_gateway.nat_1c.id}" 221 | } 222 | 223 | resource "aws_route" "private_1d" { 224 | destination_cidr_block = "0.0.0.0/0" 225 | route_table_id = "${aws_route_table.private_1d.id}" 226 | nat_gateway_id = "${aws_nat_gateway.nat_1d.id}" 227 | } 228 | 229 | # Association (Private) 230 | # https://www.terraform.io/docs/providers/aws/r/route_table_association.html 231 | resource "aws_route_table_association" "private_1a" { 232 | subnet_id = "${aws_subnet.private_1a.id}" 233 | route_table_id = "${aws_route_table.private_1a.id}" 234 | } 235 | 236 | resource "aws_route_table_association" "private_1c" { 237 | subnet_id = "${aws_subnet.private_1c.id}" 238 | route_table_id = "${aws_route_table.private_1c.id}" 239 | } 240 | 241 | resource "aws_route_table_association" "private_1d" { 242 | subnet_id = "${aws_subnet.private_1d.id}" 243 | route_table_id = "${aws_route_table.private_1d.id}" 244 | } 245 | 246 | # SecurityGroup 247 | # https://www.terraform.io/docs/providers/aws/r/security_group.html 248 | resource "aws_security_group" "alb" { 249 | name = "handson-alb" 250 | description = "handson alb" 251 | vpc_id = "${aws_vpc.main.id}" 252 | 253 | egress { 254 | from_port = 0 255 | to_port = 0 256 | protocol = "-1" 257 | cidr_blocks = ["0.0.0.0/0"] 258 | } 259 | 260 | tags = { 261 | Name = "handson-alb" 262 | } 263 | } 264 | 265 | # SecurityGroup Rule 266 | # https://www.terraform.io/docs/providers/aws/r/security_group.html 267 | resource "aws_security_group_rule" "alb_http" { 268 | security_group_id = "${aws_security_group.alb.id}" 269 | 270 | type = "ingress" 271 | 272 | from_port = 80 273 | to_port = 80 274 | protocol = "tcp" 275 | 276 | cidr_blocks = ["0.0.0.0/0"] 277 | } 278 | 279 | # ALB 280 | # https://www.terraform.io/docs/providers/aws/d/lb.html 281 | resource "aws_lb" "main" { 282 | load_balancer_type = "application" 283 | name = "handson" 284 | 285 | security_groups = ["${aws_security_group.alb.id}"] 286 | subnets = ["${aws_subnet.public_1a.id}", "${aws_subnet.public_1c.id}", "${aws_subnet.public_1d.id}"] 287 | } 288 | 289 | # Listener 290 | # https://www.terraform.io/docs/providers/aws/r/lb_listener.html 291 | resource "aws_lb_listener" "main" { 292 | # HTTPでのアクセスを受け付ける 293 | port = "80" 294 | protocol = "HTTP" 295 | 296 | # ALBのarnを指定します。 297 | #XXX: arnはAmazon Resource Names の略で、その名の通りリソースを特定するための一意な名前(id)です。 298 | load_balancer_arn = "${aws_lb.main.arn}" 299 | 300 | # "ok" という固定レスポンスを設定する 301 | default_action { 302 | type = "fixed-response" 303 | 304 | fixed_response { 305 | content_type = "text/plain" 306 | status_code = "200" 307 | message_body = "ok" 308 | } 309 | } 310 | } 311 | 312 | # Task Definition 313 | # https://www.terraform.io/docs/providers/aws/r/ecs_task_definition.html 314 | resource "aws_ecs_task_definition" "main" { 315 | family = "handson" 316 | 317 | # Fargateの設定 318 | cpu = "256" 319 | memory = "512" 320 | network_mode = "awsvpc" 321 | requires_compatibilities = ["FARGATE"] 322 | 323 | # 起動するコンテナの定義 324 | container_definitions = < 154 | arn: 155 | assign_generated_ipv6_cidr_block: "false" 156 | cidr_block: "10.0.0.0/16" 157 | default_network_acl_id: 158 | default_route_table_id: 159 | default_security_group_id: 160 | dhcp_options_id: 161 | enable_classiclink: 162 | enable_classiclink_dns_support: 163 | enable_dns_hostnames: 164 | enable_dns_support: "true" 165 | instance_tenancy: "default" 166 | ipv6_association_id: 167 | ipv6_cidr_block: 168 | main_route_table_id: 169 | owner_id: 170 | tags.%: "1" 171 | tags.Name: "vpc-handson" 172 | 173 | 174 | Plan: 1 to add, 0 to change, 0 to destroy. 175 | 176 | Do you want to perform these actions? 177 | Terraform will perform the actions described above. 178 | Only 'yes' will be accepted to approve. 179 | 180 | Enter a value: 181 | ``` 182 | 183 | > Plan: 1 to add, 0 to change, 0 to destroy. 184 | 185 | 新しく1つのリソースが追加されることが確認できました。 186 | ログを見て問題なければ `yes` で実行を継続しましょう 187 | 188 | ```console 189 | Enter a value: yes 190 | 191 | aws_vpc.main: Creating... 192 | arn: "" => "" 193 | assign_generated_ipv6_cidr_block: "" => "false" 194 | cidr_block: "" => "10.0.0.0/16" 195 | default_network_acl_id: "" => "" 196 | default_route_table_id: "" => "" 197 | default_security_group_id: "" => "" 198 | dhcp_options_id: "" => "" 199 | enable_classiclink: "" => "" 200 | enable_classiclink_dns_support: "" => "" 201 | enable_dns_hostnames: "" => "" 202 | enable_dns_support: "" => "true" 203 | instance_tenancy: "" => "default" 204 | ipv6_association_id: "" => "" 205 | ipv6_cidr_block: "" => "" 206 | main_route_table_id: "" => "" 207 | owner_id: "" => "" 208 | tags.%: "" => "1" 209 | tags.Name: "" => "vpc-handson" 210 | aws_vpc.main: Creation complete after 7s (ID: vpc-028d784cfaa5ca479) 211 | 212 | Apply complete! Resources: 1 added, 0 changed, 0 destroyed. 213 | ``` 214 | 215 | コンソールを確認して実際にVPCが起動できていることを確認してみましょう 216 | 217 | [https://ap-northeast-1.console.aws.amazon.com/vpc/home?region=ap-northeast-1#vpcs:sort=desc:VpcId](https://ap-northeast-1.console.aws.amazon.com/vpc/home?region=ap-northeast-1#vpcs:sort=desc:VpcId) 218 | 219 | ![describe-vpcs](imgs/describe-vpcs.png) 220 | 221 | ### リソースの変更を行う 222 | VPCの名前を "vpc-handson" から "vpc-handson-hoge" に変更してみましょう。 223 | `main.tf` を以下の通り編集し、VPCの命名を変更します。 224 | ```diff 225 | provider "aws" { 226 | region = "ap-northeast-1" 227 | } 228 | 229 | resource "aws_vpc" "main" { 230 | cidr_block = "10.0.0.0/16" 231 | 232 | tags = { 233 | - Name = "vpc-handson" 234 | + Name = "vpc-handson-hoge" 235 | } 236 | } 237 | ``` 238 | 239 | Terraformではコードを適用する前に、どのような変更がかかるのか確認(所謂dry-run)を行うことができます。 240 | 実際にどのような変更がかかるのか確認してみましょう。 241 | 242 | ```console 243 | # terraform plan 244 | The refreshed state will be used to calculate this plan, but will not be 245 | persisted to local or remote state storage. 246 | 247 | aws_vpc.main: Refreshing state... (ID: vpc-001c076e2cd2e79af) 248 | 249 | ------------------------------------------------------------------------ 250 | 251 | An execution plan has been generated and is shown below. 252 | Resource actions are indicated with the following symbols: 253 | ~ update in-place 254 | 255 | Terraform will perform the following actions: 256 | 257 | ~ aws_vpc.main 258 | tags.Name: "vpc-handson" => "vpc-handson-hoge" 259 | 260 | 261 | Plan: 0 to add, 1 to change, 0 to destroy. 262 | 263 | ------------------------------------------------------------------------ 264 | 265 | Note: You didn't specify an "-out" parameter to save this plan, so Terraform 266 | can't guarantee that exactly these actions will be performed if 267 | "terraform apply" is subsequently run. 268 | ``` 269 | 270 | 変更の差分が検出でき、その差分を出力した後にコマンドが終了しました。 271 | Terraformを実行する際はオペミスを防ぐためにも `terraform apply` で実行する前に、 `terraform plan` でどのような変更がかかるのか確認してから実行するのが定石です。 272 | 273 | どのような変更が行われるか検出できたので、実際に変更の適用を行いましょう。 274 | 275 | ``` 276 | # terraform apply 277 | : 278 | ``` 279 | 280 | 適用が完了したらVPCのコンソールから変更が適用できたか確認してみましょう。 281 | 282 | 283 | ### Terraformが管理しているリソース 284 | Terraformが管理しているリソースは `terraform show` コマンドで閲覧することが出来ます。 285 | 286 | まずはコマンドを打って確認してみましょう。 287 | ``` 288 | # terraform show 289 | aws_vpc.main: 290 | id = vpc-07be0a5e024877c48 291 | arn = arn:aws:ec2:ap-northeast-1:856925507022:vpc/vpc-07be0a5e024877c48 292 | assign_generated_ipv6_cidr_block = false 293 | cidr_block = 10.0.0.0/16 294 | default_network_acl_id = acl-0a00c5fa26f28e853 295 | default_route_table_id = rtb-0681966791d3ad652 296 | default_security_group_id = sg-0a5a4b4a19b9d4240 297 | dhcp_options_id = dopt-23aa3947 298 | enable_classiclink = false 299 | enable_classiclink_dns_support = false 300 | enable_dns_hostnames = false 301 | enable_dns_support = true 302 | instance_tenancy = default 303 | ipv6_association_id = 304 | ipv6_cidr_block = 305 | main_route_table_id = rtb-0681966791d3ad652 306 | owner_id = 856925507022 307 | tags.% = 1 308 | tags.Name = vpc-handson-hoge 309 | ``` 310 | 311 | Terraformが管理しているリソースは `terraform.tfstate` というJSONファイルに格納されます。 312 | Terraformコードの適用を行う際はこのファイルを参照し、差分の確認を行っています。 313 | 314 | ```console 315 | # ls 316 | main.tf terraform.tfstate terraform.tfstate.backup 317 | # cat terraform.tfstate 318 | { 319 | "version": 3, 320 | "terraform_version": "0.11.13", 321 | "serial": 5, 322 | "lineage": "40f395d7-8d59-b14c-f92f-e836258630af", 323 | "modules": [ 324 | { 325 | "path": [ 326 | "root" 327 | ], 328 | ``` 329 | 330 | Terraformは **インフラをコードで宣言する** ためのツールです。 331 | そのため、コードで定義された状態になるように動作します。 332 | 333 | 現在のapplyが完了した状態で `terraform plan` コマンドを打っても差分が検出されないことを確認しましょう 334 | ```console 335 | # terraform plan 336 | ``` 337 | 338 | !!! note "terraform.tfstateをクラウドで管理する" 339 | 前提として `terraform.tfstate` をローカル上で管理するのは危険です。 340 | Terraformはこのファイルを参照してコードの状態を適用するので、このファイルが存在しないと差分が検知できずに新しくリソースが作成されてしまいます。 341 | もし「ローカルマシンが壊れた場合」「複数人で開発したい場合」など、プロダクション開発で使用する場合はバックアップを行う必要があります。 342 | そのバックアップの機能としてTerraformは "backend" という `terraform.tfstate` をS3などのオブジェクトストレージに保管する機能が存在するので、本番でTerraformを使用する場合は検討しましょう。 343 | 344 | ## 削除する 345 | Terraformで管理されているリソースを削除しましょう。 346 | ワンコマンドで管理しているリソースを削除できます。 347 | 348 | 実際に削除してみましょう。 349 | ```console 350 | # terraform destroy 351 | aws_vpc.main: Refreshing state... (ID: vpc-001c076e2cd2e79af) 352 | 353 | An execution plan has been generated and is shown below. 354 | Resource actions are indicated with the following symbols: 355 | - destroy 356 | 357 | Terraform will perform the following actions: 358 | 359 | - aws_vpc.main 360 | 361 | 362 | Plan: 0 to add, 0 to change, 1 to destroy. 363 | 364 | Do you really want to destroy all resources? 365 | Terraform will destroy all your managed infrastructure, as shown above. 366 | There is no undo. Only 'yes' will be accepted to confirm. 367 | 368 | Enter a value: 369 | ``` 370 | 371 | 削除を実行するか確認がでるので、問題なければ `yes` と入力します 372 | 373 | ```console 374 | Enter a value: yes 375 | 376 | aws_vpc.main: Destroying... (ID: vpc-001c076e2cd2e79af) 377 | aws_vpc.main: Destruction complete after 1s 378 | 379 | Destroy complete! Resources: 1 destroyed. 380 | ``` 381 | 382 | WebコンソールでTerraformで作成したVPCが削除されていることを確認しましょう。 383 | -------------------------------------------------------------------------------- /mkdocs/docs/first/imgs/aws.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-ohgi/introduction-terraform/26c1f53e708cba26cba09464988a907b3880d035/mkdocs/docs/first/imgs/aws.png -------------------------------------------------------------------------------- /mkdocs/docs/first/imgs/describe-vpcs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-ohgi/introduction-terraform/26c1f53e708cba26cba09464988a907b3880d035/mkdocs/docs/first/imgs/describe-vpcs.png -------------------------------------------------------------------------------- /mkdocs/docs/first/imgs/handson-vpc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-ohgi/introduction-terraform/26c1f53e708cba26cba09464988a907b3880d035/mkdocs/docs/first/imgs/handson-vpc.png -------------------------------------------------------------------------------- /mkdocs/docs/first/imgs/iac-logos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-ohgi/introduction-terraform/26c1f53e708cba26cba09464988a907b3880d035/mkdocs/docs/first/imgs/iac-logos.png -------------------------------------------------------------------------------- /mkdocs/docs/first/imgs/iam-accesskey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-ohgi/introduction-terraform/26c1f53e708cba26cba09464988a907b3880d035/mkdocs/docs/first/imgs/iam-accesskey.png -------------------------------------------------------------------------------- /mkdocs/docs/first/imgs/iam-createconfirm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-ohgi/introduction-terraform/26c1f53e708cba26cba09464988a907b3880d035/mkdocs/docs/first/imgs/iam-createconfirm.png -------------------------------------------------------------------------------- /mkdocs/docs/first/imgs/iam-inputeusername.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-ohgi/introduction-terraform/26c1f53e708cba26cba09464988a907b3880d035/mkdocs/docs/first/imgs/iam-inputeusername.png -------------------------------------------------------------------------------- /mkdocs/docs/first/imgs/iam-policyattach.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-ohgi/introduction-terraform/26c1f53e708cba26cba09464988a907b3880d035/mkdocs/docs/first/imgs/iam-policyattach.png -------------------------------------------------------------------------------- /mkdocs/docs/first/imgs/iam-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-ohgi/introduction-terraform/26c1f53e708cba26cba09464988a907b3880d035/mkdocs/docs/first/imgs/iam-search.png -------------------------------------------------------------------------------- /mkdocs/docs/first/imgs/iam-tag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-ohgi/introduction-terraform/26c1f53e708cba26cba09464988a907b3880d035/mkdocs/docs/first/imgs/iam-tag.png -------------------------------------------------------------------------------- /mkdocs/docs/first/imgs/iam-top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-ohgi/introduction-terraform/26c1f53e708cba26cba09464988a907b3880d035/mkdocs/docs/first/imgs/iam-top.png -------------------------------------------------------------------------------- /mkdocs/docs/first/imgs/iam-usercreate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-ohgi/introduction-terraform/26c1f53e708cba26cba09464988a907b3880d035/mkdocs/docs/first/imgs/iam-usercreate.png -------------------------------------------------------------------------------- /mkdocs/docs/first/imgs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-ohgi/introduction-terraform/26c1f53e708cba26cba09464988a907b3880d035/mkdocs/docs/first/imgs/logo.png -------------------------------------------------------------------------------- /mkdocs/docs/first/imgs/tf-docker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-ohgi/introduction-terraform/26c1f53e708cba26cba09464988a907b3880d035/mkdocs/docs/first/imgs/tf-docker.png -------------------------------------------------------------------------------- /mkdocs/docs/first/preparation.md: -------------------------------------------------------------------------------- 1 | ![tf-docker](imgs/tf-docker.png) 2 | 3 | TerraformはCLI上から `terraform` というコマンドを打つことで実行することができます。 4 | この章では `terraform` コマンドをDocker上で動かすための準備をします。 5 | 6 | !!! note "なぜDockerでハンズオンを行うのか" 7 | Dockerはパッケージングを行うための技術です。 8 | サーバーを起動するだけではなく、バージョンの固定や不要なミドルウェアの無いクリーンな環境を用意することが可能です。 9 | コンテナ内だけで完結するためホストマシンの環境を汚さずに使用することができます。 10 | また、ホストマシン上でterraformを使用したい場合は直接terraformコマンドをインストールせず、バージョン管理が可能なツール(e.g. `tfenv` )を使用してインストールすることを推奨します。 11 | 12 | 13 | ## IAMユーザーの作成 14 | まずはAWSのWebコンソールへアクセスしてTerraform実行用のIAMユーザーを作成し、アクセスキーを取得します。 15 | 16 | AWS WebコンソールからIAMサービスへアクセス 17 | 18 | ![IAM search](imgs/iam-search.png) 19 | 20 | IAMサービスのトップページからユーザー一覧へアクセス。 21 | 22 | ![IAM top](imgs/iam-top.png) 23 | 24 | 25 | 「ユーザーを追加」を選択し、IAMユーザーの作成開始。 26 | 27 | ![IAM User Create](imgs/iam-usercreate.png) 28 | 29 | ユーザー名を「Terraform」に設定し、「プログラムによるアクセス」を有効化。 30 | 「次のステップ:アクセス権限」を押下。 31 | 32 | ![IAM input terraform](imgs/iam-inputeusername.png) 33 | 34 | 「既存のポリシーを直接アタッチ」を選択し、「AdministratorAccess」ポリシー(管理者権限)を選択。 35 | 「次のステップ:タグ」を押下。 36 | 37 | ![IAM Policy attach](imgs/iam-policyattach.png) 38 | 39 | タグは入力せず、そのまま「次のステップ:確認」を押下。 40 | 41 | ![IAM tag](imgs/iam-tag.png) 42 | 43 | 「ユーザーの作成」を押下し、IAMユーザーの作成が完了。 44 | 45 | ![IAM confirm](imgs/iam-createconfirm.png) 46 | 47 | アクセスキーを取得できるので、他人にもれないように控える。 48 | 49 | ![IAM access key](imgs/iam-accesskey.png) 50 | 51 | 52 | !!! シークレットキーをgitへ上げないための保険 53 | git commit時にcommitの内容をスキャンし、AWSアクセスキーが含まれていたらそのcommitをリジェクトしてくれるツール"git-secret"が存在します。 54 | 保険のためにもAWSを扱うリポジトリでは有効化しておくと良いでしょう。 55 | 56 | [awslabs/git-secrets: Prevents you from committing secrets and credentials into git repositories](https://github.com/awslabs/git-secrets) 57 | 58 | --- 59 | ## terraformをDockerで起動 60 | まずはディレクトリの作成をしてチェックアウトします。 61 | Desktopへ"terraform-handson"というディレクトリを作成しますが、気になる方は適宜修正してください。 62 | ```console 63 | $ mkdir ~/Desktop/terraform-handson 64 | $ cd ~/Desktop/terraform-handson 65 | ``` 66 | 67 | 次に、TerraformをDockerで起動します。 68 | その際に先程取得したIAMユーザーのアクセスキーとシークレットキーを渡すことでTerraformがAWSのAPIへアクセスできるようになります。 69 | 70 | また、ホストのカレントディレクトリをコンテナ上へマウント( `-v $(pwd):/terraform` )してファイルの共有を行います。 71 | 今後の記述するTerraformのコードはこのディレクトリに配置してコンテナと共有します。 72 | 73 | ```console 74 | $ docker run \ 75 | -e AWS_ACCESS_KEY_ID= \ 76 | -e AWS_SECRET_ACCESS_KEY= \ 77 | -v $(pwd):/terraform \ 78 | -w /terraform \ 79 | -it \ 80 | --entrypoint=ash \ 81 | hashicorp/terraform:0.11.13 82 | ``` 83 | 84 | これでTerraformの実行環境が手に入りました 85 | 86 | 最後に、Docker内でTerraformを操作するためのコマンド `terraform` が動くか試してみましょう。 87 | ```console 88 | # terraform version 89 | Terraform v0.11.13 90 | ``` 91 | -------------------------------------------------------------------------------- /mkdocs/docs/handson/about.md: -------------------------------------------------------------------------------- 1 | ## 目標 2 | ![aws](imgs/aws.png) 3 | 4 | 上記の画像が今回のハンズオンで構築を行う構成図になります。 5 | 目標としているのは「nginxの起動とそのhttps化」です。 6 | 7 | また、このハンズオンで使用するコードは以下です。 8 | 9 | [https://github.com/y-ohgi/introduction-terraform/tree/master/handson/handson](https://github.com/y-ohgi/introduction-terraform/tree/master/handson/handson) 10 | 11 | ## 使用するサービスと用途 12 | ハンズオンで構築するリソースについて概要を説明します。 13 | 14 | ### Route 53 15 | - Hosted Zone 16 | - ドメインのレコードの管理 17 | 18 | ### ACM 19 | - Certificate 20 | - AWSでTLS証明書の発行/管理を行う。 21 | 22 | ### VPC 23 | - VPC 24 | - AWS上へ仮想的なネットワークを作成する 25 | - Subnet 26 | - VPC上へ小規模な仮想的なネットワークを作成する 27 | - 今回はPublic SubnetとPrivate Subnetの2種類を3個ずつ(AZ分)作成する。 28 | - Internet Gateway 29 | - VPCはデフォルトだとIN/OUTともにインターネットへの疎通は行えないため、インターネットへの出入り口を作るリソース 30 | - Route Table 31 | - ネットワークの経路情報を設定するためのサービス 32 | - Subnetはデフォルトだとインターネットへ疎通できないため、Route Table でSubnetとInternet Gatewayを紐づけて疎通を可能にする必要がある 33 | - NAT Gateway 34 | - Private Subnetをインターネットへ疎通 35 | 36 | !!! note "Private Subnet と Public Subnet の違い" 37 | インターネットとの相互的な経路を設定されたSubnetをPublic Subnetと呼び、インターネットへの経路を設定していないSubnetをPrivate Subnetと呼びます。 38 | インターネットへの経路の設定の仕方はSubnetをRouteTableでInternet Gateway と紐付けることで実現します。 39 | 使い分けは単純で、外部に公開するもの(ロードバランサや踏み台)はPublic Subnetで、公開しないもの(Web/AppサーバやDB)はPrivate Subnetへ配置します。 40 | 41 | ### EC2 42 | - Application Load Balancer 43 | - L7ロードバランサ 44 | - 受け取ったトラフィックを紐付けられているTarget Group で管理されているサーバーへ渡す 45 | - Target Group 46 | - ロードバランサ配下のサーバーの管理 47 | 48 | ### ECS 49 | - Task Definition 50 | - コンテナの定義 51 | - Service 52 | - Task Definitionで定義されたコンテナを動かす 53 | - 動かしたコンテナをロードバランサへ紐付ける 54 | -------------------------------------------------------------------------------- /mkdocs/docs/handson/alb.md: -------------------------------------------------------------------------------- 1 | ## この章の目標 2 | ![ecs-nginx-design.png](imgs/ecs-nginx-design.png) 3 | 4 | ## ALB 5 | ![alb](imgs/alb-http-pong.png) 6 | 7 | 上記の図が目標となる構成です。ネットワークは簡略化しています。 8 | 9 | httpリクエストを受け付けるロードバランサ(ALB)と、そのALBへのhttpリクエストを許可するセキュリティグループを作成します。 10 | 11 | ```ruby 12 | # SecurityGroup 13 | # https://www.terraform.io/docs/providers/aws/r/security_group.html 14 | resource "aws_security_group" "alb" { 15 | name = "handson-alb" 16 | description = "handson alb" 17 | vpc_id = "${aws_vpc.main.id}" 18 | 19 | # セキュリティグループ内のリソースからインターネットへのアクセスを許可する 20 | egress { 21 | from_port = 0 22 | to_port = 0 23 | protocol = "-1" 24 | cidr_blocks = ["0.0.0.0/0"] 25 | } 26 | 27 | tags = { 28 | Name = "handson-alb" 29 | } 30 | } 31 | 32 | # SecurityGroup Rule 33 | # https://www.terraform.io/docs/providers/aws/r/security_group.html 34 | resource "aws_security_group_rule" "alb_http" { 35 | security_group_id = "${aws_security_group.alb.id}" 36 | 37 | # セキュリティグループ内のリソースへインターネットからのアクセスを許可する 38 | type = "ingress" 39 | 40 | from_port = 80 41 | to_port = 80 42 | protocol = "tcp" 43 | 44 | cidr_blocks = ["0.0.0.0/0"] 45 | } 46 | 47 | # ALB 48 | # https://www.terraform.io/docs/providers/aws/d/lb.html 49 | resource "aws_lb" "main" { 50 | load_balancer_type = "application" 51 | name = "handson" 52 | 53 | security_groups = ["${aws_security_group.alb.id}"] 54 | subnets = ["${aws_subnet.public_1a.id}", "${aws_subnet.public_1c.id}", "${aws_subnet.public_1d.id}"] 55 | } 56 | ``` 57 | 58 | コードの適用を行うとリソースが2つ追加されれば成功です。 59 | ```console 60 | # terraform plan 61 | : 62 | Plan: 3 to add, 0 to change, 0 to destroy. 63 | : 64 | # terraform apply 65 | : 66 | ``` 67 | 68 | コンソール上で確認するとALBが起動していて、DNSを確認することが出来ます。 69 | 70 | ![alb dns](imgs/alb-dns.png) 71 | [https://ap-northeast-1.console.aws.amazon.com/ec2/v2/home?region=ap-northeast-1#LoadBalancers:sort=loadBalancerName](https://ap-northeast-1.console.aws.amazon.com/ec2/v2/home?region=ap-northeast-1#LoadBalancers:sort=loadBalancerName) 72 | 73 | ただし、この時点ではロードバランサの設定(リスナーの追加)を行っていないためALBにアクセスできません。 74 | "リスナー" を確認すると設定が1つもないことを確認することができます。 75 | ![alb-empty-listener](imgs/alb-empty-listener.png) 76 | 77 | ## リスナーの設定 78 | ALBに登録されているDNSへhttpでアクセスすると "ok" の固定レスポンスを返すようにしましょう。 79 | 80 | "ok" を返すためにはALBのリスナーの設定を追加する必要があります。 81 | Terraformでその設定を追加しましょう。 82 | 83 | ```ruby 84 | # Listener 85 | # https://www.terraform.io/docs/providers/aws/r/lb_listener.html 86 | resource "aws_lb_listener" "main" { 87 | # HTTPでのアクセスを受け付ける 88 | port = "80" 89 | protocol = "HTTP" 90 | 91 | # ALBのarnを指定します。 92 | #XXX: arnはAmazon Resource Names の略で、その名の通りリソースを特定するための一意な名前(id)です。 93 | load_balancer_arn = "${aws_lb.main.arn}" 94 | 95 | # "ok" という固定レスポンスを設定する 96 | default_action { 97 | type = "fixed-response" 98 | 99 | fixed_response { 100 | content_type = "text/plain" 101 | status_code = "200" 102 | message_body = "ok" 103 | } 104 | } 105 | } 106 | ``` 107 | 108 | コードの適用を行うとリソースが1つ追加されれば成功です。 109 | ```console 110 | # terraform plan 111 | : 112 | Plan: 1 to add, 0 to change, 0 to destroy. 113 | : 114 | # terraform apply 115 | : 116 | ``` 117 | 118 | ALBがhttpを受け付けるようになったことを確認します。 119 | 120 | ![alb-http-listener](imgs/alb-http-listener.png) 121 | 122 | ALBのDNSへアクセスし、 "ok" が返ってくることを確認しましょう。 123 | 124 | ![alb dns](imgs/alb-dns.png) 125 | ![alb fixed response](imgs/alb-fixed-response.png) 126 | -------------------------------------------------------------------------------- /mkdocs/docs/handson/ecs.md: -------------------------------------------------------------------------------- 1 | ## ECSとは 2 | 3 | **"Elastic Container Service"** はAWSが開発したDockerオーケストレーションツールです。 4 | 開発者はコンテナを動かすことに集中できるよう開発されていてクラスターの管理はAWSが行ってくれます。 5 | そのため、プロダクションで活用するまでの学習コストが低く、他のAWSサービスとの連携もしやすいことが特徴です。 6 | 7 | ### 構成 8 | ![arch](imgs/ecs-control-data.png) 9 | 10 | ECSは **"コントロールプレーン"** と **"データプレーン"** の2つに別れます。 11 | 12 | データプレーンは1台以上のEC2をリソースプールとして扱うもので、データプレーン上にコンテナは配置されます。 13 | コントロールプレーンはデータプレーン上へコンテナの配置とその管理を行います。 14 | 15 | 基本的に開発者はEC2を立てて、ECSのコンソール上で「どんなコンテナが必要か」定義するだけであとはよしなにコンテナが動きます。 16 | 17 | !!! note "Fargate" 18 | データプレーンを構成するEC2郡は開発者が管理する必要があります。 19 | OS/ライブラリのアップデート/セキュリティパッチや使わなくなったファイルのローテート、リソースが足りなくなった際にスケールさせるなどを行う必要があります。 20 | Fargateは **"データプレーンの第二の選択肢"**です。 21 | コンテナを動かすリソースプールとして、開発者が管理するEC2郡ではなくAWSがマネージするリソースプールを使用します。 22 | これによりEC2の管理から手離れができ学習コスト/運用コストが低くなります。反面、EC2をうまく使った時より利用料は高くなります。 23 | 基本的にAWSに不慣れな場合はデータプレーンにEC2ではなくFargateを選択したほうが良いでしょう。 24 | ![fargate](imgs/ecs-fargate.png) 25 | 26 | ### コンテナの管理 27 | ![ecs](imgs/ecs.png) 28 | ECSでリクエストを受け付けるコンテナを起動する場合、基本的に上記のような構成になります。 29 | 30 | ECSを使用する際に重要なのは "タスク定義", "サービス", "タスク"の3つです。 31 | 32 | * タスク定義 33 | - どんなコンテナをどんな設定で動かすかを定義する 34 | * サービス 35 | - どのタスク定義でコンテナを立ち上げ、そのコンテナとどのロードバランサ(ターゲットグループ, リスナー)と紐付けるか 36 | * タスク 37 | - タスク定義をもとに起動したコンテナをタスクと呼びます 38 | 39 | 開発者がECSを使用する際は「タスク定義を作成し、そのタスク定義をもとにサービスを起動」する流れになります。 40 | 41 | ## タスク定義の作成 42 | ![td](imgs/ecs-td.png) 43 | 44 | nginxを立ち上げるための設定を記述します。 45 | 46 | ```ruby 47 | # Task Definition 48 | # https://www.terraform.io/docs/providers/aws/r/ecs_task_definition.html 49 | resource "aws_ecs_task_definition" "main" { 50 | family = "handson" 51 | 52 | # データプレーンの選択 53 | requires_compatibilities = ["FARGATE"] 54 | 55 | # ECSタスクが使用可能なリソースの上限 56 | # タスク内のコンテナはこの上限内に使用するリソースを収める必要があり、メモリが上限に達した場合OOM Killer にタスクがキルされる 57 | cpu = "256" 58 | memory = "512" 59 | 60 | # ECSタスクのネットワークドライバ 61 | # Fargateを使用する場合は"awsvpc"決め打ち 62 | network_mode = "awsvpc" 63 | 64 | # 起動するコンテナの定義 65 | # 「nginxを起動し、80ポートを開放する」設定を記述。 66 | container_definitions = < 99 | arn: 100 | assign_generated_ipv6_cidr_block: "false" 101 | cidr_block: "10.0.0.0/16" 102 | default_network_acl_id: 103 | default_route_table_id: 104 | default_security_group_id: 105 | dhcp_options_id: 106 | enable_classiclink: 107 | enable_classiclink_dns_support: 108 | enable_dns_hostnames: 109 | enable_dns_support: "true" 110 | instance_tenancy: "default" 111 | ipv6_association_id: 112 | ipv6_cidr_block: 113 | main_route_table_id: 114 | owner_id: 115 | tags.%: "1" 116 | tags.Name: "handson" 117 | 118 | 119 | Plan: 1 to add, 0 to change, 0 to destroy. 120 | 121 | ------------------------------------------------------------------------ 122 | 123 | Note: You didn't specify an "-out" parameter to save this plan, so Terraform 124 | can't guarantee that exactly these actions will be performed if 125 | "terraform apply" is subsequently run. 126 | ``` 127 | ```console 128 | # terraform apply 129 | : 130 | ``` 131 | 132 | 実際にVPCが作成されたかの確認をしましょう。 133 | 134 | ![vpc-list](imgs/vpc-list.png) 135 | [https://ap-northeast-1.console.aws.amazon.com/vpc/home?region=ap-northeast-1#vpcs:sort=desc:VpcId](https://ap-northeast-1.console.aws.amazon.com/vpc/home?region=ap-northeast-1#vpcs:sort=desc:VpcId) 136 | 137 | ## Subnet 138 | ![subnet](imgs/network-subnet.png) 139 | 次にサブネットを6つ作成します。 140 | Public SubnetとPrivate Subnetの2種類と、ap-northeast-1リージョン(東京リージョン)に存在する3つのAZへ各種リソース(EC2やECSやRDSなど)を配置したいため、2*3で計6つのサブネットを作成します。 141 | 142 | まずは「"handson-public-1a"という命名でap-northeast-1aにCIDRが10.0.1.0/24のサブネット」を作成してみましょう 143 | ```ruby 144 | # Subnet 145 | # https://www.terraform.io/docs/providers/aws/r/subnet.html 146 | resource "aws_subnet" "public_1a" { 147 | # 先程作成したVPCを参照し、そのVPC内にSubnetを立てる 148 | vpc_id = "${aws_vpc.main.id}" 149 | 150 | # Subnetを作成するAZ 151 | availability_zone = "ap-northeast-1a" 152 | 153 | cidr_block = "10.0.1.0/24" 154 | 155 | tags = { 156 | Name = "handson-public-1a" 157 | } 158 | } 159 | ``` 160 | 161 | planを実行し、Subnetが作成されることを確認しましょう。 162 | ```console 163 | # terraform plan 164 | Refreshing Terraform state in-memory prior to plan... 165 | The refreshed state will be used to calculate this plan, but will not be 166 | persisted to local or remote state storage. 167 | 168 | aws_vpc.main: Refreshing state... (ID: vpc-0396987ded1c4fd87) 169 | 170 | ------------------------------------------------------------------------ 171 | 172 | An execution plan has been generated and is shown below. 173 | Resource actions are indicated with the following symbols: 174 | + create 175 | 176 | Terraform will perform the following actions: 177 | 178 | + aws_subnet.public_1a 179 | id: 180 | arn: 181 | assign_ipv6_address_on_creation: "false" 182 | availability_zone: "ap-northeast-1a" 183 | availability_zone_id: 184 | cidr_block: "10.0.1.0/24" 185 | ipv6_cidr_block: 186 | ipv6_cidr_block_association_id: 187 | map_public_ip_on_launch: "false" 188 | owner_id: 189 | tags.%: "1" 190 | tags.Name: "handson-public-1a" 191 | vpc_id: "vpc-0396987ded1c4fd87" 192 | 193 | 194 | Plan: 1 to add, 0 to change, 0 to destroy. 195 | 196 | ------------------------------------------------------------------------ 197 | 198 | Note: You didn't specify an "-out" parameter to save this plan, so Terraform 199 | can't guarantee that exactly these actions will be performed if 200 | "terraform apply" is subsequently run. 201 | ``` 202 | 203 | 問題なければapplyを実行してSubnetの作成を行います。 204 | ```console 205 | # terraform apply 206 | : 207 | ``` 208 | 209 | 以下のコードを追記して、残り5つのサブネットも作成します。 210 | リソース名(e.g. "publi\_1c", "public\_1d")と各種プロパティが少しずつことなるので注意してください。 211 | ```ruby 212 | resource "aws_subnet" "public_1c" { 213 | vpc_id = "${aws_vpc.main.id}" 214 | 215 | availability_zone = "ap-northeast-1c" 216 | 217 | cidr_block = "10.0.2.0/24" 218 | 219 | tags = { 220 | Name = "handson-public-1c" 221 | } 222 | } 223 | 224 | resource "aws_subnet" "public_1d" { 225 | vpc_id = "${aws_vpc.main.id}" 226 | 227 | availability_zone = "ap-northeast-1d" 228 | 229 | cidr_block = "10.0.3.0/24" 230 | 231 | tags = { 232 | Name = "handson-public-1d" 233 | } 234 | } 235 | 236 | # Private Subnets 237 | resource "aws_subnet" "private_1a" { 238 | vpc_id = "${aws_vpc.main.id}" 239 | 240 | availability_zone = "ap-northeast-1a" 241 | cidr_block = "10.0.10.0/24" 242 | 243 | tags = { 244 | Name = "handson-private-1a" 245 | } 246 | } 247 | 248 | resource "aws_subnet" "private_1c" { 249 | vpc_id = "${aws_vpc.main.id}" 250 | 251 | availability_zone = "ap-northeast-1c" 252 | cidr_block = "10.0.20.0/24" 253 | 254 | tags = { 255 | Name = "handson-private-1c" 256 | } 257 | } 258 | 259 | resource "aws_subnet" "private_1d" { 260 | vpc_id = "${aws_vpc.main.id}" 261 | 262 | availability_zone = "ap-northeast-1d" 263 | cidr_block = "10.0.30.0/24" 264 | 265 | tags = { 266 | Name = "handson-private-1d" 267 | } 268 | } 269 | ``` 270 | 271 | planを実行し、5つの新しいリソースが追加されることを確認しましょう。 272 | ```console 273 | # terraform plan 274 | : 275 | Plan: 5 to add, 0 to change, 0 to destroy. 276 | ``` 277 | 278 | 問題なければapplyを実行してSubnetの作成を行います。 279 | ```console 280 | # terraform apply 281 | ``` 282 | 283 | WebコンソールからSubnetが6つ作成されていることを確認しましょう。 284 | サイドバーのVPCでフィルタリングで先程Terraformから作成した "handson" というVPCを選択すると分かりやすいです。 285 | 286 | ![subnets](imgs/subnet-list.png) 287 | [https://ap-northeast-1.console.aws.amazon.com/vpc/home?region=ap-northeast-1#subnets:sort=tag:Name](https://ap-northeast-1.console.aws.amazon.com/vpc/home?region=ap-northeast-1#subnets:sort=tag:Name) 288 | 289 | ## Internet Gateway 290 | ![subnet](imgs/network-igw.png) 291 | VPCからインターネットへの出入り口となるInternet Gatewayを作成しましょう。 292 | コンソール上から作成するとInternet Gateway とVPCは自動で紐付きませんが、Terraformの場合プロパティでVPCを指定することで自動的に紐づけてくれます。 293 | 294 | ```ruby 295 | # Internet Gateway 296 | # https://www.terraform.io/docs/providers/aws/r/internet_gateway.html 297 | resource "aws_internet_gateway" "main" { 298 | vpc_id = "${aws_vpc.main.id}" 299 | 300 | tags = { 301 | Name = "handson" 302 | } 303 | } 304 | ``` 305 | 306 | planを実行し、1つのリソースが追加されていることを確認します。 307 | ```console 308 | # terraform plan 309 | Plan: 1 to add, 0 to change, 0 to destroy. 310 | ``` 311 | 312 | 問題なければapplyを実行します。 313 | ```console 314 | # terraform apply 315 | ``` 316 | 317 | ## NAT Gateway 318 | ![subnet](imgs/network-natgw.png) 319 | プライベートサブネットからインターネットへ通信するためにNAT Gatewayを使用します。 320 | NAT Gatewayは1つのElastic IPが必要なのでその割り当てと、AZ毎に必要なので3つ作成します。 321 | 322 | まずはap-northeast-1a用のNAT Gatewayを1つ作成してみましょう 323 | ```ruby 324 | # Elasti IP 325 | # https://www.terraform.io/docs/providers/aws/r/eip.html 326 | resource "aws_eip" "nat_1a" { 327 | vpc = true 328 | 329 | tags = { 330 | Name = "handson-natgw-1a" 331 | } 332 | } 333 | 334 | # NAT Gateway 335 | # https://www.terraform.io/docs/providers/aws/r/nat_gateway.html 336 | resource "aws_nat_gateway" "nat_1a" { 337 | subnet_id = "${aws_subnet.public_1a.id}" # NAT Gatewayを配置するSubnetを指定 338 | allocation_id = "${aws_eip.nat_1a.id}" # 紐付けるElasti IP 339 | 340 | tags = { 341 | Name = "handson-1a" 342 | } 343 | } 344 | ``` 345 | 346 | planで変更確認を行います。 347 | ```console 348 | # terraform plan 349 | Refreshing Terraform state in-memory prior to plan... 350 | The refreshed state will be used to calculate this plan, but will not be 351 | persisted to local or remote state storage. 352 | 353 | aws_vpc.main: Refreshing state... (ID: vpc-0396987ded1c4fd87) 354 | aws_subnet.public_1d: Refreshing state... (ID: subnet-0f8eb38d6429200be) 355 | aws_internet_gateway.main: Refreshing state... (ID: igw-0e46a72941683c00f) 356 | aws_subnet.private_1a: Refreshing state... (ID: subnet-0b4e88abdc1cf586a) 357 | aws_subnet.private_1d: Refreshing state... (ID: subnet-0910f1d541d1ad52d) 358 | aws_subnet.private_1c: Refreshing state... (ID: subnet-01e1173f6b8ed56d5) 359 | aws_subnet.public_1c: Refreshing state... (ID: subnet-0519598224db34049) 360 | aws_subnet.public_1a: Refreshing state... (ID: subnet-08b90fd31e5d8dec4) 361 | 362 | ------------------------------------------------------------------------ 363 | 364 | An execution plan has been generated and is shown below. 365 | Resource actions are indicated with the following symbols: 366 | + create 367 | 368 | Terraform will perform the following actions: 369 | 370 | + aws_eip.nat_1a 371 | id: 372 | allocation_id: 373 | association_id: 374 | domain: 375 | instance: 376 | network_interface: 377 | private_dns: 378 | private_ip: 379 | public_dns: 380 | public_ip: 381 | public_ipv4_pool: 382 | tags.%: "1" 383 | tags.Name: "handson-natgw-1a" 384 | vpc: "true" 385 | 386 | + aws_nat_gateway.nat_1a 387 | id: 388 | allocation_id: "${aws_eip.nat_1a.id}" 389 | network_interface_id: 390 | private_ip: 391 | public_ip: 392 | subnet_id: "subnet-08b90fd31e5d8dec4" 393 | 394 | 395 | Plan: 2 to add, 0 to change, 0 to destroy. 396 | 397 | ------------------------------------------------------------------------ 398 | 399 | Note: You didn't specify an "-out" parameter to save this plan, so Terraform 400 | can't guarantee that exactly these actions will be performed if 401 | "terraform apply" is subsequently run. 402 | ``` 403 | 404 | Elastic IP とNAT Gateway の2つが作成されることが確認できました。 405 | 406 | applyを実行します。 407 | ```console 408 | # terraform apply 409 | ``` 410 | 411 | 残り2つのNAT Gatewayも作成して適用しましょう 412 | ```ruby 413 | resource "aws_eip" "nat_1c" { 414 | vpc = true 415 | 416 | tags = { 417 | Name = "handson-natgw-1c" 418 | } 419 | } 420 | 421 | resource "aws_nat_gateway" "nat_1c" { 422 | subnet_id = "${aws_subnet.public_1c.id}" 423 | allocation_id = "${aws_eip.nat_1c.id}" 424 | 425 | tags = { 426 | Name = "handson-1c" 427 | } 428 | } 429 | 430 | resource "aws_eip" "nat_1d" { 431 | vpc = true 432 | 433 | tags = { 434 | Name = "handson-natgw-1d" 435 | } 436 | } 437 | 438 | resource "aws_nat_gateway" "nat_1d" { 439 | subnet_id = "${aws_subnet.public_1d.id}" 440 | allocation_id = "${aws_eip.nat_1d.id}" 441 | 442 | tags = { 443 | Name = "handson-1d" 444 | } 445 | } 446 | ``` 447 | 448 | ```console 449 | # terraform plan 450 | : 451 | Plan: 6 to add, 0 to change, 0 to destroy. 452 | : 453 | # terraform apply 454 | : 455 | ``` 456 | 457 | ## Route Table 458 | 最後に、トラフィックを疎通させるための経路設定を行います。 459 | Internet Gatewayを使用してインターネットへ疎通するためのRoute Table/Routes と NAT Gatewayを経由してインターネットへ疎通するためのRoute Table/Routes を設定します。 460 | 461 | !!! note "Subnetの呼び分け" 462 | Internet Gatewayと直接的な経路が存在するSubnetを"Public Subnet"と呼び、 463 | インターネットへの経路が存在しない・NAT Gatewayを使用してインターネットへ接続しているSubnetを"Private Subnet" と呼びます 464 | 465 | ![routes](imgs/network-routes-public.png) 466 | 467 | まずはInternet GatewayとSubnetの経路を作成しましょう。 468 | 少し多いので解説すると、作成するのは以下の3種類のリソースです。 469 | 470 | 1. "aws\_route\_table" 471 | - 経路情報の格納 472 | 2. "aws\_route" 473 | - Route Tableへ経路情報を追加 474 | - インターネット(0.0.0.0/0)へ接続する際はInternet Gatewayを使用するように設定する 475 | 3. "aws\_route\_table\_association" 476 | - Route TableとSubnetの紐づけ 477 | 478 | ```ruby 479 | # Route Table 480 | # https://www.terraform.io/docs/providers/aws/r/route_table.html 481 | resource "aws_route_table" "public" { 482 | vpc_id = "${aws_vpc.main.id}" 483 | 484 | tags = { 485 | Name = "handson-public" 486 | } 487 | } 488 | 489 | # Route 490 | # https://www.terraform.io/docs/providers/aws/r/route.html 491 | resource "aws_route" "public" { 492 | destination_cidr_block = "0.0.0.0/0" 493 | route_table_id = "${aws_route_table.public.id}" 494 | gateway_id = "${aws_internet_gateway.main.id}" 495 | } 496 | 497 | # Association 498 | # https://www.terraform.io/docs/providers/aws/r/route_table_association.html 499 | resource "aws_route_table_association" "public_1a" { 500 | subnet_id = "${aws_subnet.public_1a.id}" 501 | route_table_id = "${aws_route_table.public.id}" 502 | } 503 | 504 | resource "aws_route_table_association" "public_1c" { 505 | subnet_id = "${aws_subnet.public_1c.id}" 506 | route_table_id = "${aws_route_table.public.id}" 507 | } 508 | 509 | resource "aws_route_table_association" "public_1d" { 510 | subnet_id = "${aws_subnet.public_1d.id}" 511 | route_table_id = "${aws_route_table.public.id}" 512 | } 513 | ``` 514 | 515 | ```console 516 | # terraform plan 517 | : 518 | Plan: 5 to add, 0 to change, 0 to destroy. 519 | : 520 | # terraform apply 521 | : 522 | ``` 523 | 524 | ![routes](imgs/network-routes-private.png) 525 | 526 | NAT GatewayとSubnetの経路を作成しましょう。 527 | Internet Gateway との違いとしては各AZにNAT Gateway が必要になる点です。 528 | 529 | ```ruby 530 | # Route Table (Private) 531 | # https://www.terraform.io/docs/providers/aws/r/route_table.html 532 | resource "aws_route_table" "private_1a" { 533 | vpc_id = "${aws_vpc.main.id}" 534 | 535 | tags = { 536 | Name = "handson-private-1a" 537 | } 538 | } 539 | 540 | resource "aws_route_table" "private_1c" { 541 | vpc_id = "${aws_vpc.main.id}" 542 | 543 | tags = { 544 | Name = "handson-private-1c" 545 | } 546 | } 547 | 548 | resource "aws_route_table" "private_1d" { 549 | vpc_id = "${aws_vpc.main.id}" 550 | 551 | tags = { 552 | Name = "handson-private-1d" 553 | } 554 | } 555 | 556 | # Route (Private) 557 | # https://www.terraform.io/docs/providers/aws/r/route.html 558 | resource "aws_route" "private_1a" { 559 | destination_cidr_block = "0.0.0.0/0" 560 | route_table_id = "${aws_route_table.private_1a.id}" 561 | nat_gateway_id = "${aws_nat_gateway.nat_1a.id}" 562 | } 563 | 564 | resource "aws_route" "private_1c" { 565 | destination_cidr_block = "0.0.0.0/0" 566 | route_table_id = "${aws_route_table.private_1c.id}" 567 | nat_gateway_id = "${aws_nat_gateway.nat_1c.id}" 568 | } 569 | 570 | resource "aws_route" "private_1d" { 571 | destination_cidr_block = "0.0.0.0/0" 572 | route_table_id = "${aws_route_table.private_1d.id}" 573 | nat_gateway_id = "${aws_nat_gateway.nat_1d.id}" 574 | } 575 | 576 | # Association (Private) 577 | # https://www.terraform.io/docs/providers/aws/r/route_table_association.html 578 | resource "aws_route_table_association" "private_1a" { 579 | subnet_id = "${aws_subnet.private_1a.id}" 580 | route_table_id = "${aws_route_table.private_1a.id}" 581 | } 582 | 583 | resource "aws_route_table_association" "private_1c" { 584 | subnet_id = "${aws_subnet.private_1c.id}" 585 | route_table_id = "${aws_route_table.private_1c.id}" 586 | } 587 | 588 | resource "aws_route_table_association" "private_1d" { 589 | subnet_id = "${aws_subnet.private_1d.id}" 590 | route_table_id = "${aws_route_table.private_1d.id}" 591 | } 592 | ``` 593 | 594 | ```console 595 | # terraform plan 596 | : 597 | Plan: 9 to add, 0 to change, 0 to destroy. 598 | : 599 | # terraform apply 600 | : 601 | ``` 602 | 603 | 経路設定が行えているかWebコンソール上から確認してみましょう。 604 | 605 | 確認ポイント 606 | 607 | > 1. "handson-" という名前からはじまるRoute Tableが4つあるか 608 | > 2. "handson-public" に3つSubnetが登録されているか 609 | > 3. "handson-public" に登録されているSubnetはPublic Subnetの命名になっているか 610 | > 4. "handson-public" の0.0.0.0への経路はInternet Gatewayを使用しているか 611 | > 5. "handson-private-*" は3つ存在し、それぞれ1つずつSubnetを持っているか 612 | > 6. "handson-private-*" は0.0.0.0への経路はNAT Gatewayを使用しているか 613 | 614 | ![rtb-list](imgs/rtb-list.png) 615 | [https://ap-northeast-1.console.aws.amazon.com/vpc/home?region=ap-northeast-1#RouteTables:sort=routeTableId](https://ap-northeast-1.console.aws.amazon.com/vpc/home?region=ap-northeast-1#RouteTables:sort=routeTableId) 616 | 617 | ここまでで基礎となるネットワークリソースの作成は完了です! 618 | -------------------------------------------------------------------------------- /mkdocs/docs/imgs/aws.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-ohgi/introduction-terraform/26c1f53e708cba26cba09464988a907b3880d035/mkdocs/docs/imgs/aws.png -------------------------------------------------------------------------------- /mkdocs/docs/index.md: -------------------------------------------------------------------------------- 1 | ## About 2 | Terraformについてハンズオン形式で学ぶドキュメントです。 3 | なるべく本番環境に近い環境をTerraformで構築し、「実務ですばやくTerraformを記述/導入できるようになる」ことが目的です。 4 | 5 | ## Version 6 | - Docker 7 | - 18.09.3 8 | - docker-compose 9 | - 1.23.2 10 | - Terraform 11 | - 0.11.13 12 | 13 | ## 必要な環境 14 | - AWSアカウント 15 | - [クラウドならアマゾン ウェブ サービス 【AWS 公式】](https://aws.amazon.com/jp/) 16 | - Docker for Mac/Windows 17 | - [Docker CE — Docker-docs-ja 17.06.Beta ドキュメント](http://docs.docker.jp/engine/installation/docker-ce.html) 18 | - Mac: `$ brew cask install docker` 19 | - AWS CLI 20 | - [AWS Command Line Interface をインストールする - AWS Command Line Interface](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-install.html) 21 | - Mac: `$ brew install awscli` 22 | - Windows: `> choco install awscli` 23 | - ドメイン 24 | - https化を行うため、Route53上でドメインを管理している必要があります。 25 | -------------------------------------------------------------------------------- /mkdocs/docs/laravel/about.md: -------------------------------------------------------------------------------- 1 | ![architecture](imgs/architecture.png) 2 | 3 | ここまではTerraformに慣れることを目的にハンズオンを行いました。 4 | ここからはPHPのフレームワーク "Laravel" を使用し、実際にECSとRDSを使用して構築します。 5 | 6 | サンプルコードは以下にあるので、つまったら見てください。 7 | ```console 8 | $ git clone https://github.com/y-ohgi/introduction-terraform-example 9 | ``` 10 | 11 | ## 準備 12 | [シンタックスの活用](../../handson/syntax) で例に上げたコードを追加する形で勧めます。 13 | 作業用のディレクトリを作成し、コピーするところから初めましょう。 14 | 15 | ```console 16 | $ cd ~/Desktop/ 17 | $ git clone https://github.com/y-ohgi/introduction-terraform 18 | $ mkdir laravel 19 | $ cp -R introduction-terraform/handson/syntax laravel/terraform 20 | $ cd laravel 21 | $ ls 22 | terraform 23 | ``` 24 | 25 | ```console 26 | $ cd terraform 27 | $ vi main.tf # variableで定義しているドメイン名の修正 28 | $ terraform init 29 | $ terraform apply 30 | ``` 31 | 32 | 独自ドメインでnginxの画面が見れれば成功です。 33 | 今回はnginxを使用しないので、コメントアウトしましょう。 34 | -------------------------------------------------------------------------------- /mkdocs/docs/laravel/cleanup.md: -------------------------------------------------------------------------------- 1 | ```console 2 | $ terraform destroy 3 | ``` 4 | -------------------------------------------------------------------------------- /mkdocs/docs/laravel/docker-compose.md: -------------------------------------------------------------------------------- 1 | ## 概要 2 | このハンズオンではLaravelをECS上で動かします。 3 | その前に今回立ち上げるLaravelをdocker-composeを使用してローカル環境を立ち上げてみましょう。 4 | 5 | 6 | ## 準備 7 | 先程作成したディレクトリへ入り、Laravelプロジェクトの初期化を行います。 8 | 9 | ```console 10 | $ cd ~/Desktop/laravel 11 | $ docker run -v `pwd`:/app -w /app composer create-project --prefer-dist laravel/laravel laravel 12 | $ ls 13 | laravel terraform 14 | ``` 15 | 16 | ## Dockerコンポーネントの準備 17 | docker-composeを立ち上げるために、Laravelプロジェクトのdockerizeをします。 18 | 19 | まずはlaravelディレクトリが存在するか確認しましょう 20 | ```console 21 | $ cd laravel 22 | $ ls 23 | app bootstrap composer.lock database phpunit.xml readme.md routes storage vendor 24 | artisan composer.json config package.json public resources server.php tests webpack.mix.js 25 | ``` 26 | 27 | Laravelを起動するために今回はnginxも使用します。 28 | nginxとLaravelのDocker用ファイルを用意していきましょう 29 | 30 | ### Laravel 31 | まずはLaravelの設定から行いましょう。 32 | 33 | ```console 34 | $ vi Dockerfile 35 | ``` 36 | ```Dockerfile 37 | FROM php:7.2-fpm-alpine 38 | 39 | ARG UID=991 40 | ARG UNAME=www 41 | ARG GID=991 42 | ARG GNAME=www 43 | 44 | ENV WORKDIR=/var/www/html 45 | WORKDIR $WORKDIR 46 | 47 | COPY ./docker/php/php.ini /usr/local/etc/php 48 | COPY composer.json composer.lock ${WORKDIR}/ 49 | 50 | RUN set -x \ 51 | && apk add --no-cache git php7-zlib zlib-dev \ 52 | && docker-php-ext-install pdo_mysql zip \ 53 | && curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \ 54 | && composer install --no-autoloader --no-progress --no-dev 55 | 56 | COPY . . 57 | 58 | RUN set -x \ 59 | && composer install --no-progress --no-dev \ 60 | && php artisan config:clear \ 61 | && addgroup ${GNAME} -g ${GID} \ 62 | && adduser -D -G ${GNAME} -u ${UID} ${UNAME} \ 63 | && chown -R ${UNAME}:${GNAME} ${WORKDIR} \ 64 | && mv /root/.composer /home/${UNAME}/ \ 65 | && chown -R ${UNAME}:${GNAME} /home/${UNAME} 66 | 67 | USER ${UNAME} 68 | ``` 69 | 70 | PHPを使用する場合はPHPの設定ファイル `php.ini` が欲しいため、その作成を行います。 71 | ```console 72 | $ mkdir -p docker/php/php.ini 73 | $ vi docker/php/php.ini 74 | ``` 75 | ``` 76 | [php] 77 | expose_php = Off 78 | default_charset = "UTF-8" 79 | max_execution_time = 30 80 | memory_limit = 128M 81 | file_uploads = On 82 | upload_max_filesize = 5M 83 | post_max_size = 5M 84 | ``` 85 | 86 | laravel自体の設定ファイルを記載します。 87 | ハンズオンの簡易化のためにdocker-composeで `.env` ではなく `.env.example` から環境変数の設定を読み込みます。プロダクションでは厳密に管理したほうが良いでしょう。 88 | 89 | 環境変数から不要な設定を外し、ローカルでLaravelからMySQLへの接続設定をします。 90 | 91 | ```console 92 | $ vi .env.example 93 | ``` 94 | ``` 95 | APP_NAME=handson 96 | APP_ENV=local 97 | APP_KEY= 98 | APP_KEY=base64:p5Fu8gRUOuPUXzY3VcxpnYsUR9f2h8nTSm5JlYkzPTM= 99 | APP_DEBUG=true 100 | APP_URL=http://localhost 101 | 102 | LOG_CHANNEL=stderr 103 | 104 | DB_CONNECTION=mysql 105 | DB_HOST=mysql 106 | DB_DATABASE=app 107 | DB_USERNAME=root 108 | DB_PASSWORD= 109 | ``` 110 | 111 | ここまででLaravelの設定は終わりです。 112 | 113 | ### nginx 114 | 従来のホスト上で環境構築をする場合nginxを立てるのは煩わしいだけでしたが、dockerのおかげで簡単に立ち上げることができるようになりました。 115 | プロダクション環境との際を小さくするためにも、ローカルでもnginxを立ててしまいましょう。 116 | 117 | nginxにきたトラフィックをphpへ渡す設定を書きます。 118 | ```console 119 | $ mkdir docker/nginx 120 | $ vi docker/nginx/default.conf.template 121 | ``` 122 | ```nginx 123 | server { 124 | listen 80; 125 | 126 | add_header X-Frame-Options "SAMEORIGIN"; 127 | add_header X-XSS-Protection "1; mode=block"; 128 | add_header X-Content-Type-Options "nosniff"; 129 | 130 | root /var/www/html/public/; 131 | charset utf-8; 132 | 133 | location = /favicon.ico { access_log off; log_not_found off; } 134 | location = /robots.txt { access_log off; log_not_found off; } 135 | 136 | location / { 137 | try_files $uri /index.php?$query_string; 138 | } 139 | 140 | location ~ [^/]\.php(/|$) { 141 | fastcgi_pass ${PHP_HOST}:9000; 142 | fastcgi_split_path_info ^(.+?\.php)(/.*)$; 143 | fastcgi_index index.php; 144 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 145 | fastcgi_param PATH_INFO $fastcgi_path_info; 146 | fastcgi_param REQUEST_FILENAME $request_filename; 147 | include fastcgi_params; 148 | } 149 | } 150 | ``` 151 | 152 | nginx用Dockerfileの記載を行います。 153 | 154 | ```Dockerfile 155 | FROM nginx:1.15-alpine 156 | 157 | COPY public /var/www/html/public 158 | COPY docker/nginx/default.conf.template /etc/nginx/conf.d/default.conf.template 159 | 160 | ENV PHP_HOST=127.0.0.1 161 | 162 | EXPOSE 80 163 | 164 | CMD /bin/sh -c 'sed "s/\${PHP_HOST}/${PHP_HOST}/" /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf && nginx -g "daemon off;"' 165 | ``` 166 | 167 | 168 | nginxは1点ポイントがあります。 169 | ネットワークドライバによってDNS経由からローカルホスト経由か、ネットワークが異なるため、 `default.conf.template` で `${PHP_HOST}` で一旦変数(?)にします。 170 | ``` 171 | fastcgi_pass ${PHP_HOST}:9000; 172 | ``` 173 | 174 | そしてDocker起動時に `sed` で書き換えます。 175 | デフォルトはDockerfile内の `ENV PHP_HOST=127.0.0.1` にて `127.0.0.1` を指定してローカルホストにしています。 176 | ``` 177 | CMD /bin/sh -c 'sed "s/\${PHP_HOST}/${PHP_HOST}/" /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf && nginx -g "daemon off;"' 178 | ``` 179 | 180 | 数が多い場合 `envsubst` コマンドでも良いのですが、1箇所書き換えるだけならシンプルに `sed` の方が分かりやすいでしょう。 181 | 182 | ここまででdockerの設定は完了です。 183 | 184 | 185 | ## docker-compose 186 | 187 | 先ほど作成したDockerfileと各種設定を読み込むdocker-composeの設定を記載します。 188 | ```console 189 | $ vi docker-compose.yaml 190 | ``` 191 | ```yaml 192 | version: '3.7' 193 | 194 | services: 195 | nginx: 196 | build: 197 | context: . 198 | dockerfile: docker/nginx/Dockerfile 199 | volumes: 200 | - ./public:/var/www/html/public:ro 201 | ports: 202 | - 8001:80 203 | environment: 204 | PHP_HOST: app 205 | 206 | app: 207 | build: . 208 | env_file: 209 | - .env.example 210 | volumes: 211 | - .:/var/www/html:cached 212 | 213 | mysql: 214 | image: mysql:5.7 215 | volumes: 216 | - ./mysql:/var/lib/mysql:delegated 217 | command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci 218 | environment: 219 | MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' 220 | MYSQL_DATABASE: 'app' 221 | ports: 222 | - 3306 223 | ``` 224 | 225 | 記述しおわったら起動し、動作確認をします。 226 | ``` 227 | $ docker-compose up 228 | ``` 229 | 230 | [http://localhost:8001](http://localhost:8001) 231 | 232 | Laravelのウェルカムページが表示されれば成功です! 233 | 234 | ## MySQLを使用する 235 | ここからはLaravelとMySQLを連携してみましょう。 236 | 237 | ### migrateの実行 238 | 起動しているdocker-composeをそのままに、別のターミナルを開いて操作します。 239 | 240 | まずはmigrateの実行を行います。 241 | 既に起動しているDockerコンテナの中で `php` コマンドを打ってmigrateを行います。 242 | 243 | ``` 244 | $ cd /path/to/introduction-terraform-example/laravel 245 | $ docker-compose exec app php artisan migrate 246 | ``` 247 | 248 | ## APIの動作確認 249 | まずは `/api/books` のパスにアクセスして、何も帰ってこないことを確認します。 250 | 251 | ``` 252 | $ curl localhost:8001/api/books 253 | [] 254 | ``` 255 | 256 | 何回かPOSTリクエストを送って、データを増やしてみます。 257 | ``` 258 | $ curl -X POST localhost:8001/api/books 259 | { 260 | "title": "Ramiro Bernhard", 261 | "updated_at": "2019-03-20 04:59:05", 262 | "created_at": "2019-03-20 04:59:05", 263 | "id": 1 264 | } 265 | $ curl -X POST localhost:8001/api/books 266 | { 267 | "title": "Ramiro Bernhard", 268 | "updated_at": "2019-03-20 04:59:05", 269 | "created_at": "2019-03-20 04:59:05", 270 | "id": 2 271 | } 272 | : 273 | ``` 274 | 275 | 最後に `/api/books` へGETリクエストを送り、MySQLへデータが格納されていることを確認します。 276 | 277 | ``` 278 | $ curl localhost:8001/api/books 279 | [ 280 | { 281 | "id": 1, 282 | "title": "Ramiro Bernhard", 283 | "created_at": "2019-03-20 04:59:05", 284 | "updated_at": "2019-03-20 04:59:05" 285 | }, 286 | { 287 | "id": 2, 288 | "title": "Mr. Ford Nitzsche", 289 | "created_at": "2019-03-20 05:00:36", 290 | "updated_at": "2019-03-20 05:00:36" 291 | } 292 | ] 293 | ``` 294 | 295 | ## MySQLの中に入ってみる 296 | Dockerコンテナ上で動かしているMySQLへログインしてみます。 297 | 298 | ``` 299 | $ docker-compose exec mysql mysql 300 | Your MySQL connection id is 8 301 | Server version: 5.7.25 MySQL Community Server (GPL) 302 | 303 | Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. 304 | 305 | Oracle is a registered trademark of Oracle Corporation and/or its 306 | affiliates. Other names may be trademarks of their respective 307 | owners. 308 | 309 | Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. 310 | 311 | mysql> 312 | ``` 313 | 314 | MySQLの公式Docker Image は環境変数で定義した `app` というデータベースが作成されます。 315 | MySQLコンテナの中に入り、 `app` データベースのテーブルを一覧してみましょう。 316 | 317 | ``` 318 | mysql> use mysql 319 | Reading table information for completion of table and column names 320 | You can turn off this feature to get a quicker startup with -A 321 | 322 | Database changed 323 | mysql> show tables; 324 | +-----------------+ 325 | | Tables_in_app | 326 | +-----------------+ 327 | | books | 328 | | migrations | 329 | | password_resets | 330 | | users | 331 | +-----------------+ 332 | 4 rows in set (0.00 sec) 333 | ``` 334 | -------------------------------------------------------------------------------- /mkdocs/docs/laravel/ecr.md: -------------------------------------------------------------------------------- 1 | ## この章の目標 2 | この章ではLaravelを立ち上げるためにDockerの準備を行います。 3 | 4 | ## やること 5 | - ECRの作成 6 | - dockerのビルド 7 | - ホストマシンでやる的なこと 8 | - ECRへのpush 9 | - aws cli のログイン 10 | 11 | ### ECRの作成 12 | Dockerの保管にはAWSのDockerマネージドサービスである "Elastic Container Registry" を使用します。 13 | イメージとしてはDockerHubのAWS版のようなものです。 14 | 15 | 前提としてはECRをTerraformで管理しません。 16 | Terraformは1環境(STG/PRD毎)の定義のために使用しますが、Dockerイメージは一般的に複数環境にまたがって共通のものを使用します。 17 | IaCは非常に便利ですが、100%コード化するべきなのかは適宜判断しましょう。 18 | 19 | ECRはAWSコマンドから作成します。 20 | 21 | `nginx` と `app` (Laravel)'の2つのリポジトリを作成します。 22 | 23 | ```console 24 | $ aws ecr create-repository --repository-name nginx 25 | $ aws ecr create-repository --repository-name app 26 | ``` 27 | 28 | 作成されたかの確認します。 29 | 30 | ```console 31 | $ aws ecr describe-repositories --query 'repositories[].repositoryName' 32 | [ 33 | "nginx", 34 | "app" 35 | ] 36 | ``` 37 | 38 | ### Dockerのビルド 39 | ハンズオンリポジトリへチェックアウト 40 | ```console 41 | $ cd /path/to/introduction-terraform-example/laravel 42 | ``` 43 | 44 | nginxのビルド 45 | 46 | ```console 47 | $ export ECR_URI_NGINX=$(aws ecr describe-repositories --repository-names nginx --query 'repositories[0].repositoryUri' --output text) 48 | $ docker build -t ${ECR_URI_NGINX} -f docker/nginx/Dockerfile . 49 | ``` 50 | 51 | Laravelのビルド 52 | 53 | ```console 54 | $ export ECR_URI_APP=$(aws ecr describe-repositories --repository-names app --query 'repositories[0].repositoryUri' --output text) 55 | $ docker build -t ${ECR_URI_APP} . 56 | ``` 57 | 58 | ### ECRへのpush 59 | ECRへログインします。 60 | 61 | ```console 62 | $ $(aws ecr get-login --no-include-email) 63 | ``` 64 | 65 | nginxのpush 66 | ```console 67 | $ docker push ${ECR_URI_NGINX} 68 | ``` 69 | 70 | Laravelのpush 71 | ```console 72 | $ docker push ${ECR_URI_APP} 73 | ``` 74 | -------------------------------------------------------------------------------- /mkdocs/docs/laravel/ecs.md: -------------------------------------------------------------------------------- 1 | ## この章の目標 2 | ![laravel](imgs/ecs-laravel.png) 3 | 4 | この章ではLaravelの起動を目標にします 5 | 6 | ## リソース 7 | - ECS Service 8 | - Task Definition 9 | - IAM Role 10 | - Cloud Watch Log Group 11 | - Security Group 12 | 13 | ## コード 14 | 長くなりますが、コードを記載します。 15 | 16 | `./main.tf` 17 | ```ruby 18 | module "ecs_laravel" { 19 | source = "./ecs_laravel" 20 | 21 | name = "${var.name}" 22 | 23 | cluster_name = "${module.ecs_cluster.cluster_name}" 24 | vpc_id = "${module.network.vpc_id}" 25 | subnet_ids = "${module.network.private_subnet_ids}" 26 | https_listener_arn = "${module.elb.https_listener_arn}" 27 | 28 | db_host = "${module.rds.endpoint}" 29 | 30 | db_username = "myusername" 31 | db_password = "mypassword" 32 | db_database = "mydatabase" 33 | 34 | app_key = "base64:p5Fu8gRUOuPUXzY3VcxpnYsUR9f2h8nTSm5JlYkzPTM=" 35 | } 36 | ``` 37 | 38 | 39 | `./ecs_laravel/main.tf` 40 | ```ruby 41 | variable "name" { 42 | type = "string" 43 | } 44 | 45 | variable "vpc_id" { 46 | type = "string" 47 | } 48 | 49 | variable "https_listener_arn" { 50 | type = "string" 51 | } 52 | 53 | variable "cluster_name" { 54 | type = "string" 55 | } 56 | 57 | variable "subnet_ids" { 58 | type = "list" 59 | } 60 | 61 | variable "db_host" { 62 | type = "string" 63 | } 64 | 65 | variable "db_username" { 66 | type = "string" 67 | } 68 | 69 | variable "db_password" { 70 | type = "string" 71 | } 72 | 73 | variable "db_database" { 74 | type = "string" 75 | } 76 | 77 | variable "app_key" { 78 | type = "string" 79 | } 80 | 81 | data "aws_region" "current" {} 82 | 83 | data "aws_caller_identity" "current" {} 84 | 85 | locals { 86 | name = "${var.name}-laravel" 87 | 88 | # アカウントID 89 | account_id = "${data.aws_caller_identity.current.account_id}" 90 | 91 | # プロビジョニングを実行するリージョン 92 | region = "${data.aws_region.current.name}" 93 | } 94 | 95 | resource "aws_lb_target_group" "this" { 96 | name = "${local.name}" 97 | 98 | vpc_id = "${var.vpc_id}" 99 | 100 | port = 80 101 | target_type = "ip" 102 | protocol = "HTTP" 103 | 104 | health_check = { 105 | port = 80 106 | } 107 | } 108 | 109 | data "template_file" "container_definitions" { 110 | template = "${file("./ecs_laravel/container_definitions.json")}" 111 | 112 | vars = { 113 | tag = "latest" 114 | 115 | account_id = "${local.account_id}" 116 | region = "${local.region}" 117 | name = "${local.name}" 118 | 119 | db_host = "${var.db_host}" 120 | db_username = "${var.db_username}" 121 | db_password = "${var.db_password}" 122 | db_database = "${var.db_database}" 123 | 124 | app_key = "${var.app_key}" 125 | } 126 | } 127 | 128 | resource "aws_ecs_task_definition" "this" { 129 | family = "${local.name}" 130 | 131 | container_definitions = "${data.template_file.container_definitions.rendered}" 132 | 133 | cpu = "256" 134 | memory = "512" 135 | network_mode = "awsvpc" 136 | requires_compatibilities = ["FARGATE"] 137 | 138 | task_role_arn = "${aws_iam_role.task_execution.arn}" 139 | execution_role_arn = "${aws_iam_role.task_execution.arn}" 140 | } 141 | 142 | resource "aws_cloudwatch_log_group" "this" { 143 | name = "/${var.name}/ecs" 144 | retention_in_days = "7" 145 | } 146 | 147 | resource "aws_iam_role" "task_execution" { 148 | name = "${var.name}-TaskExecution" 149 | 150 | assume_role_policy = < 286 | 3. サブネット: <ハンズオンで作成した **プライベートサブネット** > 287 | 4. セキュリティーグループ: <ハンズオンで作成した **ECS Service用のセキュリティーグループ** > 288 | 5. パブリックIPの自動割り当て: DISABLE 289 | 6. コンテナの上書き 290 | - コマンドの上書き: `php,artisan,migrate,--force` 291 | 292 | 入力後、「タスクの実行」を押下します。 293 | 294 | ![migration](imgs/aws-laravel-migrate.png) 295 | 296 | migration結果の確認 297 | 298 | ![logs](imgs/aws-migration-cloudwatch.png) 299 | 300 | ![result](imgs/aws-migration-result.png) 301 | 302 | ## 動作確認 303 | ```console 304 | $ curl /api/books 305 | [] 306 | $ curl -X POST /api/books 307 | { 308 | "title": "tmp title", 309 | "updated_at": "2019-03-20 09:34:00", 310 | "created_at": "2019-03-20 09:34:00", 311 | "id": 1 312 | } 313 | $ curl -X POST /api/books 314 | { 315 | "title": "tmp title", 316 | "updated_at": "2019-03-20 09:34:00", 317 | "created_at": "2019-03-20 09:34:00", 318 | "id": 2 319 | } 320 | $ curl /api/books 321 | [ 322 | { 323 | "id": 1, 324 | "title": "tmp title", 325 | "created_at": "2019-03-20 09:33:53", 326 | "updated_at": "2019-03-20 09:33:53" 327 | }, 328 | { 329 | "id": 2, 330 | "title": "tmp title", 331 | "created_at": "2019-03-20 09:34:00", 332 | "updated_at": "2019-03-20 09:34:00" 333 | } 334 | ] 335 | ``` 336 | -------------------------------------------------------------------------------- /mkdocs/docs/laravel/imgs/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-ohgi/introduction-terraform/26c1f53e708cba26cba09464988a907b3880d035/mkdocs/docs/laravel/imgs/architecture.png -------------------------------------------------------------------------------- /mkdocs/docs/laravel/imgs/aurora.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-ohgi/introduction-terraform/26c1f53e708cba26cba09464988a907b3880d035/mkdocs/docs/laravel/imgs/aurora.png -------------------------------------------------------------------------------- /mkdocs/docs/laravel/imgs/aws-laravel-migrate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-ohgi/introduction-terraform/26c1f53e708cba26cba09464988a907b3880d035/mkdocs/docs/laravel/imgs/aws-laravel-migrate.png -------------------------------------------------------------------------------- /mkdocs/docs/laravel/imgs/aws-migration-cloudwatch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-ohgi/introduction-terraform/26c1f53e708cba26cba09464988a907b3880d035/mkdocs/docs/laravel/imgs/aws-migration-cloudwatch.png -------------------------------------------------------------------------------- /mkdocs/docs/laravel/imgs/aws-migration-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-ohgi/introduction-terraform/26c1f53e708cba26cba09464988a907b3880d035/mkdocs/docs/laravel/imgs/aws-migration-result.png -------------------------------------------------------------------------------- /mkdocs/docs/laravel/imgs/aws-run-task.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-ohgi/introduction-terraform/26c1f53e708cba26cba09464988a907b3880d035/mkdocs/docs/laravel/imgs/aws-run-task.png -------------------------------------------------------------------------------- /mkdocs/docs/laravel/imgs/aws-select-task.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-ohgi/introduction-terraform/26c1f53e708cba26cba09464988a907b3880d035/mkdocs/docs/laravel/imgs/aws-select-task.png -------------------------------------------------------------------------------- /mkdocs/docs/laravel/imgs/ecs-laravel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-ohgi/introduction-terraform/26c1f53e708cba26cba09464988a907b3880d035/mkdocs/docs/laravel/imgs/ecs-laravel.png -------------------------------------------------------------------------------- /mkdocs/docs/laravel/rds.md: -------------------------------------------------------------------------------- 1 | ## この章の目標 2 | ![aurora](imgs/aurora.png) 3 | 4 | この章ではAWSのマネージドデータベースであるAurora(MySQL)の起動を行います。 5 | 6 | ## リソース 7 | - Subnet Group 8 | - Parameter Group 9 | - Cluster Parameter Group 10 | - Aurora Cluster 11 | - Aurora Instance 12 | 13 | ## コード 14 | `./main.tf` 15 | ```ruby 16 | : 17 | 18 | module "rds" { 19 | source = "./rds" 20 | 21 | name = "${var.name}" 22 | 23 | vpc_id = "${module.network.vpc_id}" 24 | subnet_ids = "${module.network.private_subnet_ids}" 25 | 26 | database_name = "mydatabase" 27 | master_username = "myusername" 28 | master_password = "mypassword" 29 | } 30 | ``` 31 | 32 | `./rds/main.tf` 33 | ```ruby 34 | variable "name" { 35 | type = "string" 36 | } 37 | 38 | variable "vpc_id" { 39 | type = "string" 40 | } 41 | 42 | variable "subnet_ids" { 43 | type = "list" 44 | } 45 | 46 | variable "database_name" { 47 | type = "string" 48 | } 49 | 50 | variable "master_username" { 51 | type = "string" 52 | } 53 | 54 | variable "master_password" { 55 | type = "string" 56 | } 57 | 58 | locals { 59 | name = "${var.name}-mysql" 60 | } 61 | 62 | resource "aws_security_group" "this" { 63 | name = "${local.name}" 64 | description = "${local.name}" 65 | 66 | vpc_id = "${var.vpc_id}" 67 | 68 | egress { 69 | from_port = 0 70 | to_port = 0 71 | protocol = "-1" 72 | cidr_blocks = ["0.0.0.0/0"] 73 | } 74 | 75 | tags = { 76 | Name = "${local.name}" 77 | } 78 | } 79 | 80 | resource "aws_security_group_rule" "mysql" { 81 | security_group_id = "${aws_security_group.this.id}" 82 | 83 | type = "ingress" 84 | 85 | from_port = 3306 86 | to_port = 3306 87 | protocol = "tcp" 88 | cidr_blocks = ["10.0.0.0/16"] 89 | } 90 | 91 | resource "aws_db_subnet_group" "this" { 92 | name = "${local.name}" 93 | description = "${local.name}" 94 | subnet_ids = ["${var.subnet_ids}"] 95 | } 96 | 97 | resource "aws_rds_cluster" "this" { 98 | cluster_identifier = "${local.name}" 99 | 100 | db_subnet_group_name = "${aws_db_subnet_group.this.name}" 101 | vpc_security_group_ids = ["${aws_security_group.this.id}"] 102 | 103 | engine = "aurora-mysql" 104 | port = "3306" 105 | 106 | database_name = "${var.database_name}" 107 | master_username = "${var.master_username}" 108 | master_password = "${var.master_password}" 109 | } 110 | 111 | resource "aws_rds_cluster_instance" "this" { 112 | identifier = "${local.name}" 113 | cluster_identifier = "${aws_rds_cluster.this.id}" 114 | 115 | engine = "aurora-mysql" 116 | 117 | instance_class = "db.t3.small" 118 | } 119 | 120 | output "endpoint" { 121 | value = "${aws_rds_cluster.this.endpoint}" 122 | } 123 | ``` 124 | -------------------------------------------------------------------------------- /mkdocs/docs/next-step.md: -------------------------------------------------------------------------------- 1 | 次のステップとしてはTerrraformの学習もありますが、このドキュメントではとりあえ使いきれなかった以下の点を試してみると面白いかもしれません。 2 | 3 | 4 | - 複数環境(DEV/STG/PRD)に対応させてみる 5 | - データベースや `APP_KEY` の設定情報をコードから切り離す 6 | - CI/CD 7 | - 冗長化 8 | - 監視 9 | -------------------------------------------------------------------------------- /mkdocs/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: 'Terraformで構築するAWS' 2 | 3 | theme: 4 | name: 'material' 5 | language: 'ja' 6 | palette: 7 | primary: 'blue grey' 8 | accent: 'blue grey' 9 | font: 10 | text: 'Roboto' 11 | code: 'Roboto Mono' 12 | highlightjs: true 13 | hljs_languages: 14 | - 'yaml' 15 | - 'dockerfile' 16 | - 'ruby' 17 | 18 | extra: 19 | search: 20 | language: 'jp' 21 | 22 | markdown_extensions: 23 | - 'admonition' 24 | - 'footnotes' 25 | - codehilite: 26 | guess_lang: false 27 | - toc: 28 | permalink: true 29 | - pymdownx.tasklist: 30 | custom_checkbox: true 31 | 32 | extra_css: 33 | - 'assets/custom.css' 34 | 35 | extra_javascript: 36 | - 'assets/custom.js' 37 | 38 | nav: 39 | - 'Home': 'index.md' 40 | - 'はじめてのTerraform': 41 | - 'Terraformとは': 'first/about.md' 42 | - '環境を準備する': 'first/preparation.md' 43 | - 'とりあえず触ってみる': 'first/handson.md' 44 | - 'ハンズオン': 45 | - '概要': 'handson/about.md' 46 | - 'VPC': 'handson/vpc.md' 47 | - 'ALB': 'handson/alb.md' 48 | - 'ECS': 'handson/ecs.md' 49 | - 'https化': 'handson/https.md' 50 | - 'シンタックスの活用': 'handson/syntax.md' 51 | - 'laravelを動かす': 52 | - '概要': 'laravel/about.md' 53 | - 'docker-composeの起動': 'laravel/docker-compose.md' 54 | - 'RDS': 'laravel/rds.md' 55 | - 'ECR': 'laravel/ecr.md' 56 | - 'ECS': 'laravel/ecs.md' 57 | - '片付け': 'laravel/cleanup.md' 58 | # - 'おまけ': 59 | # - '変数を使用する': 'variable.md' 60 | # - 'ファイルの分割': 'module.md' 61 | # - '設定情報をAWSへ格納': 'handson/aurora.md' 62 | # - 'オートスケール': 'handson/cleanup.md' 63 | # - 'CI/CD': 'handson/cleanup.md' 64 | - 'Next Step': 'next-step.md' 65 | --------------------------------------------------------------------------------