├── src └── 2025_hands-on │ ├── environments │ ├── dev │ │ ├── outputs.tf │ │ ├── variables.tf │ │ ├── main.tf │ │ └── .terraform.lock.hcl │ ├── production │ │ ├── outputs.tf │ │ ├── variables.tf │ │ ├── main.tf │ │ └── .terraform.lock.hcl │ └── staging │ │ ├── outputs.tf │ │ ├── variables.tf │ │ ├── main.tf │ │ └── .terraform.lock.hcl │ └── modules │ ├── storage │ ├── variables.tf │ └── main.tf │ └── integration │ ├── variables.tf │ └── main.tf ├── .github ├── ISSUE_TEMPLATE │ ├── config.yaml │ └── generic.md ├── renovate.json5 └── workflows │ ├── textlint.yaml │ └── ci.yml ├── .gitignore ├── assets ├── run-new-ui.png ├── policy-create-newui.png ├── teams │ ├── Invite_a_user.png │ ├── create_team.png │ ├── add_team_member.png │ ├── workspace_team.png │ ├── Invite_a_user_button.png │ ├── organization_access.png │ └── workspace_permission.png └── tfc-remote-state │ ├── state-1.png │ ├── state-2.png │ ├── state-3.png │ ├── new_state.png │ ├── create-org.png │ ├── user_setting.png │ ├── execution_mode.png │ ├── generate_token.png │ ├── new_workspace.png │ ├── create-org-name.png │ ├── create-ws-new-ui.png │ ├── generated_token.png │ ├── create-ws-new-ui-2.png │ ├── account_setting_detail.png │ └── create-org-inputmail.png ├── package.json ├── _typos.toml ├── .textlintrc.json ├── README.md └── contents ├── notifications.md ├── teams.md ├── vcs.md ├── vcs-azure.md ├── module.md ├── tfc-remote-state.md ├── variables.md ├── hello-terraform.md ├── api-driven-run.md ├── tf-api.md └── sentinel.md /src/2025_hands-on/environments/dev/outputs.tf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/2025_hands-on/environments/dev/variables.tf: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/2025_hands-on/environments/production/outputs.tf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/2025_hands-on/environments/staging/outputs.tf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/2025_hands-on/environments/staging/variables.tf: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/2025_hands-on/environments/production/variables.tf: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yaml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .terraform 3 | *.tfstate* 4 | .DS_Store 5 | node_modules/ -------------------------------------------------------------------------------- /assets/run-new-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp-japan/terraform-workshop-jp/HEAD/assets/run-new-ui.png -------------------------------------------------------------------------------- /src/2025_hands-on/modules/storage/variables.tf: -------------------------------------------------------------------------------- 1 | variable "bucket_name" { 2 | type = string 3 | default = "" 4 | } 5 | -------------------------------------------------------------------------------- /src/2025_hands-on/modules/integration/variables.tf: -------------------------------------------------------------------------------- 1 | variable "sqs_queue_name" { 2 | type = string 3 | default = "" 4 | } -------------------------------------------------------------------------------- /assets/policy-create-newui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp-japan/terraform-workshop-jp/HEAD/assets/policy-create-newui.png -------------------------------------------------------------------------------- /assets/teams/Invite_a_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp-japan/terraform-workshop-jp/HEAD/assets/teams/Invite_a_user.png -------------------------------------------------------------------------------- /assets/teams/create_team.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp-japan/terraform-workshop-jp/HEAD/assets/teams/create_team.png -------------------------------------------------------------------------------- /assets/teams/add_team_member.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp-japan/terraform-workshop-jp/HEAD/assets/teams/add_team_member.png -------------------------------------------------------------------------------- /assets/teams/workspace_team.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp-japan/terraform-workshop-jp/HEAD/assets/teams/workspace_team.png -------------------------------------------------------------------------------- /assets/tfc-remote-state/state-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp-japan/terraform-workshop-jp/HEAD/assets/tfc-remote-state/state-1.png -------------------------------------------------------------------------------- /assets/tfc-remote-state/state-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp-japan/terraform-workshop-jp/HEAD/assets/tfc-remote-state/state-2.png -------------------------------------------------------------------------------- /assets/tfc-remote-state/state-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp-japan/terraform-workshop-jp/HEAD/assets/tfc-remote-state/state-3.png -------------------------------------------------------------------------------- /assets/teams/Invite_a_user_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp-japan/terraform-workshop-jp/HEAD/assets/teams/Invite_a_user_button.png -------------------------------------------------------------------------------- /assets/teams/organization_access.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp-japan/terraform-workshop-jp/HEAD/assets/teams/organization_access.png -------------------------------------------------------------------------------- /assets/teams/workspace_permission.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp-japan/terraform-workshop-jp/HEAD/assets/teams/workspace_permission.png -------------------------------------------------------------------------------- /assets/tfc-remote-state/new_state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp-japan/terraform-workshop-jp/HEAD/assets/tfc-remote-state/new_state.png -------------------------------------------------------------------------------- /assets/tfc-remote-state/create-org.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp-japan/terraform-workshop-jp/HEAD/assets/tfc-remote-state/create-org.png -------------------------------------------------------------------------------- /assets/tfc-remote-state/user_setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp-japan/terraform-workshop-jp/HEAD/assets/tfc-remote-state/user_setting.png -------------------------------------------------------------------------------- /assets/tfc-remote-state/execution_mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp-japan/terraform-workshop-jp/HEAD/assets/tfc-remote-state/execution_mode.png -------------------------------------------------------------------------------- /assets/tfc-remote-state/generate_token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp-japan/terraform-workshop-jp/HEAD/assets/tfc-remote-state/generate_token.png -------------------------------------------------------------------------------- /assets/tfc-remote-state/new_workspace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp-japan/terraform-workshop-jp/HEAD/assets/tfc-remote-state/new_workspace.png -------------------------------------------------------------------------------- /assets/tfc-remote-state/create-org-name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp-japan/terraform-workshop-jp/HEAD/assets/tfc-remote-state/create-org-name.png -------------------------------------------------------------------------------- /assets/tfc-remote-state/create-ws-new-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp-japan/terraform-workshop-jp/HEAD/assets/tfc-remote-state/create-ws-new-ui.png -------------------------------------------------------------------------------- /assets/tfc-remote-state/generated_token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp-japan/terraform-workshop-jp/HEAD/assets/tfc-remote-state/generated_token.png -------------------------------------------------------------------------------- /assets/tfc-remote-state/create-ws-new-ui-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp-japan/terraform-workshop-jp/HEAD/assets/tfc-remote-state/create-ws-new-ui-2.png -------------------------------------------------------------------------------- /assets/tfc-remote-state/account_setting_detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp-japan/terraform-workshop-jp/HEAD/assets/tfc-remote-state/account_setting_detail.png -------------------------------------------------------------------------------- /assets/tfc-remote-state/create-org-inputmail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hashicorp-japan/terraform-workshop-jp/HEAD/assets/tfc-remote-state/create-org-inputmail.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "textlint": "^15.5.0", 4 | "textlint-rule-ja-space-between-half-and-full-width": "^2.4.2", 5 | "textlint-rule-terminology": "^5.2.16" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/2025_hands-on/modules/integration/main.tf: -------------------------------------------------------------------------------- 1 | 2 | resource "aws_sqs_queue" "example_sqs" { 3 | name = var.sqs_queue_name 4 | delay_seconds = 60 5 | max_message_size = 2048 6 | message_retention_seconds = 86400 7 | receive_wait_time_seconds = 10 8 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/generic.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Generic Issue Template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Issue Overview 11 | Please describe the overview/details of issue you observed. 12 | 13 | 14 | ## (Optional) ToDo 15 | Please fill out the action items if you have already ideas for fixing issue. 16 | - [ ] 17 | - [ ] 18 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | // refs: https://docs.renovatebot.com/configuration-options/ 2 | { 3 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 4 | "extends": [ 5 | "config:base", 6 | "schedule:weekly", 7 | "group:allNonMajor", 8 | ":semanticCommits", 9 | ":semanticCommitTypeAll(chore)", 10 | ":disableDependencyDashboard" 11 | ], 12 | "labels": ["deps", "renovate"], 13 | "baseBranches": ["main"], 14 | "rangeStrategy": "bump" 15 | } 16 | -------------------------------------------------------------------------------- /src/2025_hands-on/environments/dev/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "6.26.0" 6 | } 7 | } 8 | } 9 | 10 | provider "aws" { 11 | # access_key = var.aws_access_key 12 | # secret_key = var.aws_secret_key 13 | region = "ap-northeast-1" 14 | } 15 | 16 | module "storage" { 17 | source = "./../../modules/storage" 18 | } 19 | 20 | module "integration" { 21 | source = "./../../modules/integration" 22 | } -------------------------------------------------------------------------------- /src/2025_hands-on/environments/staging/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "6.26.0" 6 | } 7 | } 8 | } 9 | 10 | provider "aws" { 11 | # access_key = var.aws_access_key 12 | # secret_key = var.aws_secret_key 13 | region = "ap-northeast-1" 14 | } 15 | 16 | module "storage" { 17 | source = "./../../modules/storage" 18 | } 19 | 20 | module "integration" { 21 | source = "./../../modules/integration" 22 | } -------------------------------------------------------------------------------- /src/2025_hands-on/environments/production/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "6.26.0" 6 | } 7 | } 8 | } 9 | 10 | provider "aws" { 11 | # access_key = var.aws_access_key 12 | # secret_key = var.aws_secret_key 13 | region = "ap-northeast-1" 14 | } 15 | 16 | module "storage" { 17 | source = "./../../modules/storage" 18 | } 19 | 20 | module "integration" { 21 | source = "./../../modules/integration" 22 | } -------------------------------------------------------------------------------- /_typos.toml: -------------------------------------------------------------------------------- 1 | [default] 2 | # // spellchecker:disable-line を行末に入れるとチェックを回避 3 | extend-ignore-re = [ 4 | "(?Rm)^.*(#|//)\\s*spellchecker:disable-line$", 5 | ] 6 | 7 | [default.extend-words] 8 | HashiCorp = "HashiCorp" 9 | Hashi = "Hashi" 10 | Terraform = "Terraform" 11 | Vault = "Vault" 12 | Consul = "Consul" 13 | Nomad = "Nomad" 14 | Vagrant = "Vagrant" 15 | Packer = "Packer" 16 | Boundary = "Boundary" 17 | Waypoint = "Waypoint" 18 | HCL = "HCL" 19 | Sentinel = "Sentinel" 20 | HCP = "HCP" 21 | InfraGraph = "InfraGraph" 22 | -------------------------------------------------------------------------------- /.github/workflows/textlint.yaml: -------------------------------------------------------------------------------- 1 | name: textlint 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "contents/**/*.md" 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | textlint-ja: 14 | runs-on: ubuntu-24.04 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v6 18 | 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v6 21 | 22 | - name: Install textlint rules 23 | run: npm install -D textlint textlint-rule-preset-ja-spacing 24 | 25 | - name: Run textlint 26 | run: | 27 | export PATH="$PATH:$(npm root)/.bin" 28 | textlint README.md ./contents/**/*.md 29 | -------------------------------------------------------------------------------- /src/2025_hands-on/modules/storage/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_s3_bucket" "example_bucket" { 2 | bucket = var.bucket_name 3 | 4 | tags = { 5 | Name = "an example bucket" 6 | } 7 | } 8 | 9 | /* 10 | * Continuous validation sample codes 11 | */ 12 | resource "aws_s3_bucket_server_side_encryption_configuration" "this" { 13 | bucket = aws_s3_bucket.example_bucket.id 14 | 15 | rule { 16 | apply_server_side_encryption_by_default { 17 | sse_algorithm = "AES256" 18 | } 19 | } 20 | } 21 | 22 | check "s3_bucket_encryption" { 23 | assert { 24 | condition = contains([ 25 | for rule in aws_s3_bucket_server_side_encryption_configuration.this.rule : 26 | rule.apply_server_side_encryption_by_default[0].sse_algorithm 27 | ], "AES256") 28 | error_message = "S3 bucket is not encrypted" 29 | } 30 | } -------------------------------------------------------------------------------- /.textlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": {}, 3 | "filters": {}, 4 | "rules": { 5 | "ja-space-between-half-and-full-width": { 6 | "space": "always", 7 | "lintStyledNode": true 8 | }, 9 | "terminology": { 10 | "defaultTerms": true, 11 | "terms": [ 12 | "HashiCorp", 13 | "HCP", 14 | "Terraform", 15 | "HCP Terraform", 16 | "Terraform Enterprise", 17 | "Vault", 18 | "HCP Vault", 19 | "HCP Vault Secrets", 20 | "Vault Enterprise", 21 | "Consul", 22 | "HCP Consul", 23 | "Consul Enterprise", 24 | "Nomad", 25 | "Nomad Enterprise", 26 | "Vagrant", 27 | "Packer", 28 | "HCP Packer", 29 | "Boundary", 30 | "HCP Boundary", 31 | "Waypoint", 32 | "HCP Waypoint", 33 | "Project Infragraph", 34 | "HCL", 35 | "Sentinel", 36 | "GitHub Actions" 37 | ] 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI Checks 2 | 3 | on: 4 | push: 5 | branches: [ "main", "master" ] 6 | pull_request: 7 | workflow_dispatch: 8 | 9 | jobs: 10 | # 1. リンク切れチェック 11 | link-check: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: read 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Link Checker 18 | uses: lycheeverse/lychee-action@v1 19 | with: 20 | args: --verbose --no-progress './**/*.md' 21 | env: 22 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 23 | 24 | # 2. タイポチェック 25 | typos-check: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v4 29 | - name: Typos source code spell checker 30 | uses: crate-ci/typos@master 31 | 32 | # 3. 表記揺れチェック (Textlint) 33 | textlint-check: 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@v4 37 | - name: Setup Node.js 38 | uses: actions/setup-node@v4 39 | with: 40 | node-version: '20' 41 | cache: 'npm' 42 | - name: Install dependencies 43 | run: npm ci 44 | - name: Run textlint 45 | run: npx textlint --config .textlintrc.json '**/*.md' -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HashiCorp Terraform Enterprise Workshop 2 | 3 | [Terraform](https://www.terraform.io/) は HashiCorp が中心に開発をする OSS のプロビジョニングツールです。このレイヤではほぼ業界標準のソフトウェアと位置付けられており、国内外のコミュニティなども非常に活発です。 4 | 5 | Terraform はインフラのプロビジョニングツールというイメージが強いですが、現在 150 以上のプロバイダに対応しており、幅広いレイヤでの利用が可能です。 6 | 7 | OSS 版ではすでに多くの情報が日本語でも調べることが可能なため、本ワークショップはエンタープライズ機能に特化した内容にしています。**Terraform のコアの機能を学習する内容ではないのでご注意ください。** 8 | 9 | ## Pre-requisite 10 | 11 | * 環境 12 | * macOS or Linux 13 | 14 | * ソフトウェア 15 | * Terraform 16 | * jq,watch,curl 17 | * git cli 18 | * aws / gcloud 19 | 20 | * アカウント 21 | * GitHub 22 | * Terraform Cloud 23 | * AWS / GCP 24 | 25 | ## 資料 26 | 27 | * [Terraform Enterprise Overview](https://docs.google.com/presentation/d/1Ovdee0FIrJ_h66B5DToQNYKWJ9XRbudS0RCk4d_x1Eg/edit?usp=sharing) 28 | 29 | ## アジェンダ 30 | * [初めての Terraform](https://github.com/hashicorp-japan/terraform-workshop/blob/master/contents/hello-terraform.md) 31 | * [Terraform Cloud によるリモートステート管理](./contents/tfc-remote-state.md) 32 | * VCS 連携 ([GitHub](https://github.com/hashicorp-japan/terraform-workshop/blob/master/contents/vcs.md), [Azure DevOps](https://github.com/hashicorp-japan/terraform-workshop/blob/master/contents/vcs-azure.md)) 33 | * [Secure Variable Storage](https://github.com/hashicorp-japan/terraform-workshop/blob/master/contents/variables.md) 34 | * [Enterprise 機能 1: RBAC](./contents/teams.md) 35 | * [Enterprise 機能 2: Policy as Code](https://github.com/hashicorp-japan/terraform-workshop/blob/master/contents/sentinel.md) 36 | * [Enterprise 機能 3: Private Module Registry](https://github.com/hashicorp-japan/terraform-workshop/blob/master/contents/module.md) 37 | * [Enterprise 機能 4: Terraform Enterprise API](https://github.com/hashicorp-japan/terraform-workshop/blob/master/contents/tf-api.md) 38 | * [Enterprise 機能 5: Notifications](https://github.com/hashicorp-japan/terraform-workshop/blob/master/contents/notifications.md) 39 | * CLI Drive Run 40 | * [API Drive Run](./contents/api-driven-run.md) 41 | * Terraform Enterprise のインストール 42 | -------------------------------------------------------------------------------- /src/2025_hands-on/environments/dev/.terraform.lock.hcl: -------------------------------------------------------------------------------- 1 | # This file is maintained automatically by "terraform init". 2 | # Manual edits may be lost in future updates. 3 | 4 | provider "registry.terraform.io/hashicorp/aws" { 5 | version = "6.26.0" 6 | constraints = "6.26.0" 7 | hashes = [ 8 | "h1:/G7Fb/OINBkXvq2LFLrPfPBxqBMdr1cqFRIAkD/XhyM=", 9 | "h1:6Wu7+MenomJx4k82rl5/lHMcP7xh0CoeCY9PhuhbcdI=", 10 | "h1:79RHpchB+IjuZLMNkbCSjkguoAOUsSWnr0N6Bei+PxI=", 11 | "h1:9+Cx2w/zhs5ROinFqFStL+l8bu1Mi+B3+WROcEHEoRI=", 12 | "h1:B7X8EU6aZ9KzIvP0VBDhhgadgjXIrUgMXt/pJ6EUXvo=", 13 | "h1:J1tK3tbpNdEvTLs00Z0aUUgovF/fC6GCRcD3p+6TXqw=", 14 | "h1:NyoEMqqQt3roRP7QB/UvZDKl5mpuSbgzqQqduB5Fo8M=", 15 | "h1:YOnnhggw+uxkhLtx96Vait5tBlSBaufCQCTYmZ+ZIyY=", 16 | "h1:bq6gvZehoO4jBx/MsjIyMFHU56DX+TP7ndl34o3Fncs=", 17 | "h1:iut0YILWmDkv5f/rMsNCiAlVz9Iop1r2s3FIGAiHatU=", 18 | "h1:kkP6pGZ57uBS2TuYjLy4SHzdU2nig9jTGYz5ikQBsyk=", 19 | "h1:pxymO0z9mzldaBpAYLY1OigD8MT0QUmH+owW+9/s+UQ=", 20 | "h1:saa2buUwCBo+WpGC96bGRbmUsl5pO13SXFeffCpLNJE=", 21 | "h1:yqvGC5L3f1fznyq+J/fGM86cWQ8A6fdMaIZvTSel1UI=", 22 | "zh:038fd943de79acd9f9f73106fa0eba588c6a0d4e0993e146f51f3aa043728c5f", 23 | "zh:06fa0177d33d3d3f9cb7e205fbeb1c4c3095ba637e2b4d292429401ec5612e81", 24 | "zh:212714fc8b6ee57e26d11d0fdf2ecfe23b37a6eac1008b399c1d790528c3f072", 25 | "zh:3197725d772f360e9e466b68a5ba67363e9f6786809c9adefc50f7f7e525bf42", 26 | "zh:33385539f3e3fafb96c6036421fd72b05c76505eeefaaff8a089c3eeba25db65", 27 | "zh:4ce065e0d3c384d11c1b59fe92582d331aae27ff6e019ace07b8cedef5653aae", 28 | "zh:67863d6ff5517db2c0b8097443708dca548f1922d2e08ad76a98d493ff480cb1", 29 | "zh:771ccf61fc107013b437b0a05cdb342823a99200653bfe9b892702b9fd8997fe", 30 | "zh:80adcf83bef9d683606c48bbe53cbb2b5a04878641674e957939b5e8f124ada0", 31 | "zh:9675c7f209db8e64ba2d5197acc8ba0073bd73b48c3dd61a1961a44844bc8a81", 32 | "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", 33 | "zh:b47d0f5eff91c94c5d5677815b9361e64dfbe2ee2d59ba2867e2d0f5fa7181e4", 34 | "zh:b4933663b8d6cc1cfb51aa47bd8f26c06012ee2e278e57663faffdc722dd5baa", 35 | "zh:d53a94ecdb6b68a8dc19ec6e16ba2d4c2acde575af254d1b8b80143e57c76abf", 36 | "zh:e7cb8c1770c6f87c5ce1d3e28b838380bb8e5296dd03034b796168de8be1c7ec", 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /src/2025_hands-on/environments/production/.terraform.lock.hcl: -------------------------------------------------------------------------------- 1 | # This file is maintained automatically by "terraform init". 2 | # Manual edits may be lost in future updates. 3 | 4 | provider "registry.terraform.io/hashicorp/aws" { 5 | version = "6.26.0" 6 | constraints = "6.26.0" 7 | hashes = [ 8 | "h1:/G7Fb/OINBkXvq2LFLrPfPBxqBMdr1cqFRIAkD/XhyM=", 9 | "h1:6Wu7+MenomJx4k82rl5/lHMcP7xh0CoeCY9PhuhbcdI=", 10 | "h1:79RHpchB+IjuZLMNkbCSjkguoAOUsSWnr0N6Bei+PxI=", 11 | "h1:9+Cx2w/zhs5ROinFqFStL+l8bu1Mi+B3+WROcEHEoRI=", 12 | "h1:B7X8EU6aZ9KzIvP0VBDhhgadgjXIrUgMXt/pJ6EUXvo=", 13 | "h1:J1tK3tbpNdEvTLs00Z0aUUgovF/fC6GCRcD3p+6TXqw=", 14 | "h1:NyoEMqqQt3roRP7QB/UvZDKl5mpuSbgzqQqduB5Fo8M=", 15 | "h1:YOnnhggw+uxkhLtx96Vait5tBlSBaufCQCTYmZ+ZIyY=", 16 | "h1:bq6gvZehoO4jBx/MsjIyMFHU56DX+TP7ndl34o3Fncs=", 17 | "h1:iut0YILWmDkv5f/rMsNCiAlVz9Iop1r2s3FIGAiHatU=", 18 | "h1:kkP6pGZ57uBS2TuYjLy4SHzdU2nig9jTGYz5ikQBsyk=", 19 | "h1:pxymO0z9mzldaBpAYLY1OigD8MT0QUmH+owW+9/s+UQ=", 20 | "h1:saa2buUwCBo+WpGC96bGRbmUsl5pO13SXFeffCpLNJE=", 21 | "h1:yqvGC5L3f1fznyq+J/fGM86cWQ8A6fdMaIZvTSel1UI=", 22 | "zh:038fd943de79acd9f9f73106fa0eba588c6a0d4e0993e146f51f3aa043728c5f", 23 | "zh:06fa0177d33d3d3f9cb7e205fbeb1c4c3095ba637e2b4d292429401ec5612e81", 24 | "zh:212714fc8b6ee57e26d11d0fdf2ecfe23b37a6eac1008b399c1d790528c3f072", 25 | "zh:3197725d772f360e9e466b68a5ba67363e9f6786809c9adefc50f7f7e525bf42", 26 | "zh:33385539f3e3fafb96c6036421fd72b05c76505eeefaaff8a089c3eeba25db65", 27 | "zh:4ce065e0d3c384d11c1b59fe92582d331aae27ff6e019ace07b8cedef5653aae", 28 | "zh:67863d6ff5517db2c0b8097443708dca548f1922d2e08ad76a98d493ff480cb1", 29 | "zh:771ccf61fc107013b437b0a05cdb342823a99200653bfe9b892702b9fd8997fe", 30 | "zh:80adcf83bef9d683606c48bbe53cbb2b5a04878641674e957939b5e8f124ada0", 31 | "zh:9675c7f209db8e64ba2d5197acc8ba0073bd73b48c3dd61a1961a44844bc8a81", 32 | "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", 33 | "zh:b47d0f5eff91c94c5d5677815b9361e64dfbe2ee2d59ba2867e2d0f5fa7181e4", 34 | "zh:b4933663b8d6cc1cfb51aa47bd8f26c06012ee2e278e57663faffdc722dd5baa", 35 | "zh:d53a94ecdb6b68a8dc19ec6e16ba2d4c2acde575af254d1b8b80143e57c76abf", 36 | "zh:e7cb8c1770c6f87c5ce1d3e28b838380bb8e5296dd03034b796168de8be1c7ec", 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /src/2025_hands-on/environments/staging/.terraform.lock.hcl: -------------------------------------------------------------------------------- 1 | # This file is maintained automatically by "terraform init". 2 | # Manual edits may be lost in future updates. 3 | 4 | provider "registry.terraform.io/hashicorp/aws" { 5 | version = "6.26.0" 6 | constraints = "6.26.0" 7 | hashes = [ 8 | "h1:/G7Fb/OINBkXvq2LFLrPfPBxqBMdr1cqFRIAkD/XhyM=", 9 | "h1:6Wu7+MenomJx4k82rl5/lHMcP7xh0CoeCY9PhuhbcdI=", 10 | "h1:79RHpchB+IjuZLMNkbCSjkguoAOUsSWnr0N6Bei+PxI=", 11 | "h1:9+Cx2w/zhs5ROinFqFStL+l8bu1Mi+B3+WROcEHEoRI=", 12 | "h1:B7X8EU6aZ9KzIvP0VBDhhgadgjXIrUgMXt/pJ6EUXvo=", 13 | "h1:J1tK3tbpNdEvTLs00Z0aUUgovF/fC6GCRcD3p+6TXqw=", 14 | "h1:NyoEMqqQt3roRP7QB/UvZDKl5mpuSbgzqQqduB5Fo8M=", 15 | "h1:YOnnhggw+uxkhLtx96Vait5tBlSBaufCQCTYmZ+ZIyY=", 16 | "h1:bq6gvZehoO4jBx/MsjIyMFHU56DX+TP7ndl34o3Fncs=", 17 | "h1:iut0YILWmDkv5f/rMsNCiAlVz9Iop1r2s3FIGAiHatU=", 18 | "h1:kkP6pGZ57uBS2TuYjLy4SHzdU2nig9jTGYz5ikQBsyk=", 19 | "h1:pxymO0z9mzldaBpAYLY1OigD8MT0QUmH+owW+9/s+UQ=", 20 | "h1:saa2buUwCBo+WpGC96bGRbmUsl5pO13SXFeffCpLNJE=", 21 | "h1:yqvGC5L3f1fznyq+J/fGM86cWQ8A6fdMaIZvTSel1UI=", 22 | "zh:038fd943de79acd9f9f73106fa0eba588c6a0d4e0993e146f51f3aa043728c5f", 23 | "zh:06fa0177d33d3d3f9cb7e205fbeb1c4c3095ba637e2b4d292429401ec5612e81", 24 | "zh:212714fc8b6ee57e26d11d0fdf2ecfe23b37a6eac1008b399c1d790528c3f072", 25 | "zh:3197725d772f360e9e466b68a5ba67363e9f6786809c9adefc50f7f7e525bf42", 26 | "zh:33385539f3e3fafb96c6036421fd72b05c76505eeefaaff8a089c3eeba25db65", 27 | "zh:4ce065e0d3c384d11c1b59fe92582d331aae27ff6e019ace07b8cedef5653aae", 28 | "zh:67863d6ff5517db2c0b8097443708dca548f1922d2e08ad76a98d493ff480cb1", 29 | "zh:771ccf61fc107013b437b0a05cdb342823a99200653bfe9b892702b9fd8997fe", 30 | "zh:80adcf83bef9d683606c48bbe53cbb2b5a04878641674e957939b5e8f124ada0", 31 | "zh:9675c7f209db8e64ba2d5197acc8ba0073bd73b48c3dd61a1961a44844bc8a81", 32 | "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", 33 | "zh:b47d0f5eff91c94c5d5677815b9361e64dfbe2ee2d59ba2867e2d0f5fa7181e4", 34 | "zh:b4933663b8d6cc1cfb51aa47bd8f26c06012ee2e278e57663faffdc722dd5baa", 35 | "zh:d53a94ecdb6b68a8dc19ec6e16ba2d4c2acde575af254d1b8b80143e57c76abf", 36 | "zh:e7cb8c1770c6f87c5ce1d3e28b838380bb8e5296dd03034b796168de8be1c7ec", 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /contents/notifications.md: -------------------------------------------------------------------------------- 1 | # Notifications を利用して Slack と連携する 2 | 3 | ここでは Terraform Enterprise の Notifications の機能を利用して Slack と連携し Terraform Enterprise のイベント通知を受信する方法を試してみます。 4 | 5 | ## Slack の設定 6 | 7 | このハンズオンを完了させるには Slack ワークスペースの`Custom Integration`の編集のできる権限が必要です。 8 | 9 | [こちら](https://hashicorp-apac.slack.com/apps/manage/custom-integrations)にアクセスして、`Incoming WebHook`の設定を行います。 10 | 11 | 12 | 13 | 14 | 15 | `Add to Slack`をクリックして、次の画面に進みます。 16 | 17 | 18 | 19 | 20 | 21 | `Post to Channel`のプルダウンで通知をしたいチャネルやダイレクトメッセージ先を選択しましょう。  22 | 23 | 24 | 25 | 26 | 27 | `Add Incoming WebHook Integaraion`をクリックすると WebHook の設定画面になります。ここで表示される`WebHook URL`をメモしておきます。 28 | 29 | 30 | **この URL は Slack のトークンも含まれるので絶対に GitHub のレポジトリ等にアップロードしてはいけません。** 31 | 32 | ## Terraform Enterprise の設定 33 | 34 | あとはこの URL を Terraform Entprise のワークスペースに設定するだけです。 35 | 36 | TFC のブラウザの`Workspaces` -> `handson-workshop` -> `Settings` -> `Notifications` -> `Create a Notification`と進んでください。 37 | 38 | 39 | 40 | 41 | 42 | General な WebHook と Slack が選択できます。Microsoft Teams や Mattermost など Slack 以外の WebHook での通知に対応しているツールと連携する際は WebHook を選択します。 43 | 44 | ここでは Slack を選択し 45 | 46 | * `Name`: `My Notification` 47 | * `WebHook URL`: 先ほどコピーしたもの 48 | * `Triggers`: `All events` 49 | 50 | を選択し`Create a Notification`をクリックします。 51 | 52 | 完了画面で`Send a Test`をクリックし、Slack に通知が来ることを確認してください。 53 | 54 | 55 | 56 | 57 | 58 | ## 実際にプロビジョニングを試す 59 | 60 | 最後に実際のプロビジョニングでどのようなメッセージが来るかを確認してみましょう。`handson-workshop`のメニューで以下のように`Queue Plan`をして下さい。 61 | 62 | 63 | 64 | 65 | 66 | `Run`の内容を確認すると、プロビジョニングのワークフローが開始され、都度 Slack に通知が来るでしょう。 67 | 68 | 69 | 以下のように通知が来ていれば成功です。 70 | 71 | 72 | 73 | 74 | 簡単ですが、Terrafrom Enterprise の通知機能を試してみました。 75 | 76 | ## 参考リンク 77 | * [Run Notifications](https://www.terraform.io/docs/cloud/workspaces/notifications.htmlz) -------------------------------------------------------------------------------- /contents/teams.md: -------------------------------------------------------------------------------- 1 | # Teams in Terraform cloud/enterpise 2 | 3 | Terraform cloud free-tier (無料版)には Team という概念がありません。よって複数のメンバーで使用するときにアクセス制御をつける事ができません。 4 | 5 | その問題を解決するために、Terraform cloud 及び Enterprise には**Roles / Team managerment**という機能が準備されています。 6 | この機能は以下の Terraform offering で利用可能です。 7 | - Terraform cloud team 8 | - Terraform cloud team & governance 9 | - Terraform Enterprise 10 | 11 | Team に対する権限は Organization レベルと Workspace レベルの 2 段階で設定できます。 12 | 13 | ここでのエクササイズでは、organization 及び Workspace に対して Team を作成し、アクセス権限を付与してみます。 14 | 15 | ## Organization レベルの Team の作成 16 | 17 | まず、[Terraform cloud](https://app.terraform.io/)へアクセスし、Organization の**Setting >> Teams**へ行きます。 18 | 19 | 20 | 21 | 22 | 23 | Team には以下のような 3 つの権限をつけることが出来ます。**ここでの権限は Organization レベルの権限であり、Workspace レベルの権限ではありません。** 24 | - Managed Policies 25 | - Manage workspaces 26 | - Manage VCS setting 27 | 28 | 29 | 30 | 31 | 32 | **Create a New Team**から以下の 3 つのチームを 1 つづつ作成します。 33 | 34 | 1. admin 35 | - admin には 3 つ全ての権限をつけて下さい。 36 | 2. developers 37 | - 権限は何もつけなくてよいです。(Organization レベルの権限は付与しない) 38 | 3. managers 39 | - 権限は何もつけなくてよいです。(Organization レベルの権限は付与しない) 40 | 41 | ## Workspace レベルの Team の作成 42 | 43 | 次に Workspace レベルの Team の作成を行います。 44 | 45 | Workspace を開き、**Settings > Team Access**へナビゲートしてください。 46 | 47 | 48 | 49 | 50 | 51 | ここで先程作成した Team に、以下のように Workspace レベルの Permissions を付与してください。 52 | 53 | - admin 54 | - admin 55 | - developers 56 | - write 57 | - managers 58 | - read 59 | 60 | 以下のように表示されれば OK です。 61 | 62 | 63 | 64 | 65 | 66 | この Permission については詳細は[ドキュメント](https://www.terraform.io/docs/cloud/users-teams-organizations/permissions.html)を参照いただければと思います。簡単にまとめますと: 67 | 68 | - Read 69 | - State ファイルの参照 70 | - Run 履歴の参照 71 | - セキュア変数の参照 72 | - State ファイルに変更の加わる処理はできない 73 | - Plan 74 | - Read 権限の全て 75 | - Run の作成(Plan の実行可、Apply の実行不可) 76 | - Write 77 | - Plan 権限の全て 78 | - State ファイルへの変更(Apply の実行) 79 | - Run 実行の承認 80 | - セキュア変数の変更 81 | - Workspace の Lock/Unlock 82 | - Admin 83 | - Write 権限の全て 84 | - Workspace の削除 85 | - Workspace へのメンバーの追加・変更 86 | - Workspace 設定(VCS など)の変更 87 | 88 | となります。 89 | 90 | ## チームメンバーを Team にアサイン 91 | 92 | Team が設定されたら、admins 権限のあるユーザーはチームメンバーを Team にアサインします。 93 | 94 | 既に Organization に追加されているユーザーであれば、**organization レベルの Settings >> Teams** から Team を選択し、**Add a New Team Member**でユーザーを追加します。 95 | 96 | 97 | 98 | 99 | 100 | これからユーザーを追加する場合は、**organization レベルの Settings >> Users**の**Invite a user**ボタンからユーザーの Team を選択してインバイトします。 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | ## まとめ 111 | 112 | Terraform cloud free-tier では Workspace へのアクセスがあれば誰でも State ファイルへ変更(Apply の実効)が可能ですが、Terraform cloud paid 及び Enterprise では、様々な処理に対しアクセス制限を付けることができます。複数のチームメンバーに対し、それぞれ役割に応じた権限を割り当てることで、ルールに応じたワークフローを実現できるのではないでしょうか。 113 | 114 | Team とアクセス制限を駆使して Terraform Cloud/Enterprise では RBAC(Role based access control)を実現します。 115 | -------------------------------------------------------------------------------- /contents/vcs.md: -------------------------------------------------------------------------------- 1 | # Version Control System 連携を試す 2 | 3 | TFC には三つの Apply のトリガーを使うことができます。 4 | 5 | * API Driven 6 | * CLI Drive 7 | * VCS Driven 8 | 9 | ここでは VCS Drive パターンの Webhook での実行を試してみます。 10 | 11 | ## GitHub と Terraform Cloud のセットアップ 12 | 13 | ここでは Terraform Cloud(以下、TFC)を使って Enterprise 版の機能を使ってみます。Terraform Cloud には機能が限定的な無償版と Enterprise のライセンスでアクティベートされる有償版があります。このハンズオンでは講師が事前に期限限定でアクティベートしてあります。 14 | 15 | TFC にアクセスし最初のセットアップを行いましょう。 16 | 17 | ワークスペースを作成し、そこに VCS との連携の設定を行います。対応している VCS は以下の通りです。 18 | 19 | * [GitHub](https://www.terraform.io/docs/cloud/vcs/github.html) 20 | * [GitHub Enterprise](https://www.terraform.io/docs/cloud/vcs/github-enterprise.html) 21 | * [GitLab.com](https://www.terraform.io/docs/cloud/vcs/gitlab-com.html) 22 | * [GitLab EE and CE](https://www.terraform.io/docs/cloud/vcs/gitlab-eece.html) 23 | * [Bitbucket Cloud](https://www.terraform.io/docs/cloud/vcs/bitbucket-cloud.html) 24 | * [Bitbucket Server](https://www.terraform.io/docs/cloud/vcs/bitbucket-server.html) 25 | * [Azure DevOps](https://www.terraform.io/docs/cloud/vcs/azure-devops-services.html) 26 | * [Azure DevOps Server](https://www.terraform.io/docs/cloud/vcs/azure-devops-services.html) 27 | 28 | 以下は GitHub の手順ですが、違う VCS で実施した場合はリンクの手順を参考にしてください。 29 | 30 | ### GitHub レポジトリ作成 31 | 32 | ``` 33 | GitHub上に"tf-handson-workshop"という名前のパブリックレポジトリを作成してください。 34 | ``` 35 | ### GitHub の OAuth Application の作成 36 | 37 | トップ画面の`Settings`からサイドメニューの`Version control` -> `Providers`を選んでください。 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 次に[こちら](https://github.com/settings/applications/new)にアクセスし TFC 用のキーを発行します。 48 | 49 | OAuth アプリケーションの登録画面で以下のように入力してください。 50 | 51 | 52 | 53 | 54 | 55 | * Application Name : Terraform Cloud 56 | * Homepage URL: `https://app.terraform.io` 57 | * Authorization callback URL: `https://example.com/replace-this-later` 58 | 59 | 入力したら`Register`をクリックします。 60 | 61 | 62 | 63 | 64 | 65 | 次に TFC に戻り`Add VCS Provider`を選択します。`Client ID`と`Client Secret`の欄に上の GitHub 上の画面で取得した値をコピペしてください。 66 | 67 | 68 | 69 | 70 | 71 | `Create`をクリックします。 72 | 73 | 74 | 75 | 76 | 77 | VCS Provider が一つ追加され、Callback URL が生成されたのでこれをコピーし、これを GitHub の`Authorization callback URL`の項目を置き換えます。 78 | 79 | 80 | 81 | 82 | 83 | これで Save しましょう。 84 | 85 | 最後にトップ画面の`Settings`からサイドメニューの`Version control` -> `Providers`から先ほど追加した GitHub の`Connect`をクリックして認証行ってください。 86 | 87 | これで VCS の設定は完了です。次にこれを紐付けたワークスペースを作成します。 88 | 89 | ## Workspace の作成 90 | 91 | まずワークスペースのレポジトリを作ります。 92 | 93 | ```shell 94 | $ cd path/to/tf-workspace 95 | $ mkdir tf-handson-workshop 96 | $ cd tf-handson-workshop 97 | ``` 98 | 99 | 以下のファイルを作ります。 100 | 101 | ```shell 102 | $ cat < main.tf 103 | terraform { 104 | } 105 | EOF 106 | ``` 107 | 108 | GitHub にプッシュして連携の確認をしてみましょう。 109 | 110 | ```shell 111 | $ export GITURL= 112 | $ git init 113 | $ git add main.tf 114 | $ git commit -m "first commit" 115 | $ git branch -M main 116 | $ git remote add origin $GITURL 117 | $ git push -u origin main 118 | ``` 119 | 120 | TFC 上で Workspace を作成します。トップ画面の`+ New Workspace`を選択しします。 121 | ワークスペース作成画面で`VCS Drive Workflow`を選択し、その後の画面で GitHub を選択します。 122 | 123 | 124 | 125 | 126 | 127 | レポジトリは先ほど作成した`tf-handson-workshop`を選択し、`Create Workspace`をクリックします。 128 | 129 | 130 | 131 | 132 | 133 | 成功の画面が出たら`Actions` -> `Start a New Plan` を実行して動作を確認してみましょう。ここでは空のコードを実行しているため`Apply will not run`となるはずです。 134 | 135 | 136 | 137 | 138 | 139 | 140 | 次以降の章で実際のコードを Apply して GitHub を通したインフラのプロビジョニングを試してみます。 141 | 142 | ## 参考リンク 143 | * [VCS Integration](https://www.terraform.io/docs/cloud/vcs/index.html) 144 | * [VCS Driven Run](https://www.terraform.io/docs/cloud/run/ui.html) 145 | -------------------------------------------------------------------------------- /contents/vcs-azure.md: -------------------------------------------------------------------------------- 1 | # Version Control System 連携を試す 2 | 3 | TFC には三つの Apply のトリガーを使うことができます。 4 | 5 | * API Driven 6 | * CLI Drive 7 | * VCS Driven 8 | 9 | ここでは VCS Drive パターンの Webhook での実行を試してみます。 10 | 11 | ## GitHub と Terraform Cloud のセットアップ 12 | 13 | ここでは Terraform Cloud(以下、TFC)を使って Enterprise 版の機能を使ってみます。Terraform Cloud には機能が限定的な無償版と Enterprise のライセンスでアクティベートされる有償版があります。このハンズオンでは講師が事前に期限限定でアクティベートしてあります。 14 | 15 | TFC にアクセスし最初のセットアップを行いましょう。 16 | 17 | ワークスペースを作成し、そこに VCS との連携の設定を行います。対応している VCS は以下の通りです。 18 | 19 | * [GitHub](https://www.terraform.io/docs/cloud/vcs/github.html) 20 | * [GitHub Enterprise](https://www.terraform.io/docs/cloud/vcs/github-enterprise.html) 21 | * [GitLab.com](https://www.terraform.io/docs/cloud/vcs/gitlab-com.html) 22 | * [GitLab EE and CE](https://www.terraform.io/docs/cloud/vcs/gitlab-eece.html) 23 | * [Bitbucket Cloud](https://www.terraform.io/docs/cloud/vcs/bitbucket-cloud.html) 24 | * [Bitbucket Server](https://www.terraform.io/docs/cloud/vcs/bitbucket-server.html) 25 | * [Azure DevOps Services](https://www.terraform.io/docs/cloud/vcs/azure-devops-services.html) 26 | * [Azure DevOps Server](https://www.terraform.io/docs/cloud/vcs/azure-devops-services.html) 27 | 28 | 以下は Azure DevOps の手順ですが、違う VCS で実施した場合はリンクの手順を参考にしてください。 29 | 30 | ### Azure DevOps Progects 作成 31 | 32 | [こちらから](https://aex.dev.azure.com/me?mkt=en-US)Organization を作成してください。次に、 33 | 34 | ``` 35 | "tf-handson-workshop"という名前のパブリックプロジェクトを作成してください。 36 | ``` 37 | ### GitHub の OAuth Application の作成 38 | 39 | トップ画面の`Settings`からサイドメニューの`Version control` -> `Providers`を選んでください。この画面はこのままにしておいてください。 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 次に[こちら](https://aex.dev.azure.com/app/register?mkt=en-US)にアクセスし TFC 用のキーを発行します。 50 | 51 | OAuth アプリケーションの登録画面で以下のように入力してください。 52 | 53 | 54 | 55 | 56 | 57 | * Company name: 58 | * Application Name : Terraform Cloud 59 | * Application website: `https://app.terraform.io` 60 | * Authorization callback URL: `https://example.com/replace-this-later` 61 | * Authorized scopes: Code(read), Code(status)にチェック 62 | 63 | 入力したら`Create Application`をクリックします。 64 | 65 | 66 | 67 | 68 | 69 | `App ID`と`Client Secret`はコピーしておいてください。この画面はこのままにしておいてください。 70 | 71 | 次に TFC に戻り`Add VCS Provider`を選択します。 72 | 73 | `Version Control System(VCS) Provider`からプルダウンで`Azure DevOps Services`を選択し、`App ID`と`Client Secret`の欄に上の Azure DevOps 上の画面で取得した値をコピペしてください。 74 | 75 | 76 | 77 | 78 | 79 | `Add VCS Provider`をクリックします。 80 | 81 | 82 | 83 | 84 | 85 | VCS Provider が一つ追加され、Callback URL が生成されたのでこれをコピーし、これを Azure DevOps の`Authorization callback URL`の項目を置き換えます。 86 | 87 | Azure DevOps の画面に戻り、`Edit Application`をクリックし、`Authorization callback URL`の`https://example.com/replace-this-later`をコピーした Callback URL に変更します。 88 | 89 | 90 | 91 | 92 | 93 | これで Save changes しましょう。 94 | 95 | 最後にトップ画面の`Settings`からサイドメニューの`Version control` -> `Providers`から先ほど追加した GitHub の`Connect organization username`をクリックして認証行ってください。 96 | 97 | これで VCS の設定は完了です。次にこれを紐付けたワークスペースを作成します。 98 | 99 | ## Workspace の作成 100 | 101 | まずワークスペースのレポジトリを作ります。 102 | 103 | ```shell 104 | $ mkdir -p tf-workspace/tf-handson-workshop 105 | $ cd tf-workspace/tf-handson-workshop 106 | ``` 107 | 108 | 以下のファイルを作ります。 109 | 110 | ```shell 111 | $ cat < main.tf 112 | terraform { 113 | } 114 | EOF 115 | ``` 116 | 117 | Azure DevOps にプッシュして連携の確認をしてみましょう。[Azure DevOps のコンソール](https://aex.dev.azure.com/)から先ほど作った Organization->`tf-handson-workshop`を選択し、左カラムの`Repos`から URL をコピーしてください。また、`Generate Git Credentials`からパスワードをコピーしておいて下さい。 118 | 119 | ```shell 120 | $ export ADO_URL= 121 | $ git init 122 | $ git add main.tf 123 | $ git commit -m "first commit" 124 | $ git branch -M main 125 | $ git remote add origin $ADO_URL 126 | $ git push -u origin main 127 | ``` 128 | 129 | TFC 上で Workspace を作成します。トップ画面の`+ New Workspace`を選択し、Azure DevOps を選択します。 130 | 131 | 132 | 133 | 134 | 135 | レポジトリは先ほど作成した`tf-handson-workshop`を選択し、`Create Workspace`をクリックします。 136 | 137 | 138 | 139 | 140 | 141 | 成功の画面が出たら`Queue Plan`を実行して動作を確認してみましょう。ここでは空のコードを実行しているため`Apply will not run`となるはずです。 142 | 143 | 144 | 145 | 146 | 147 | 148 | 次以降の章で実際のコードを Apply して GitHub を通したインフラのプロビジョニングを試してみます。 149 | 150 | ## 参考リンク 151 | * [VCS Integration](https://www.terraform.io/docs/cloud/vcs/index.html) 152 | * [VCS Driven Run](https://www.terraform.io/docs/cloud/run/ui.html) 153 | -------------------------------------------------------------------------------- /contents/module.md: -------------------------------------------------------------------------------- 1 | # Private Module Registry を試す 2 | 3 | Terraform には DRY(Don't Repeat Yourself)を実現するために共通処理のコードをモジュール化する機能があります。OSS でも利用が可能ですが Git などのレポジトリから取得するか[Public Module Registry](https://registry.terraform.io/)から取得する方法が用意されています。 4 | 5 | Enterprise 版では Organization ごとにプライベートな Module Registry を持つことが出来ます。これを利用することでチームだけに限定した Module のレジストリを介してソースコードを再利用できます。 6 | 7 | ## モジュールを作る 8 | 9 | まず Module を作っていきましょう。モジュールは TFE で対応しているコードレポジトリ上に作っていきます。まずは GitHub 上にレポジトリを作ります。 10 | 11 | ``` 12 | GitHubのアカウントに"terraform-aws-securitygroup"という名前のレポジトリを作ってください。 13 | ``` 14 | 15 | レポジトリ名は必ず`terraform--`という形にする必要があります。 16 | 17 | 次に必要なファイルを作っていきます。 18 | 19 | ```shell 20 | $ mkdir path/to/tf-workshop/terraform-aws-securitygroup 21 | $ cd path/to/tf-workshop/terraform-aws-securitygroup 22 | $ touch README.md main.tf variables.tf output.tf 23 | ``` 24 | 25 | `README.md`を以下のように編集します。 26 | 27 |
28 | README.md 29 | 30 | ``` 31 | | IP address | デフォルト | 用途 | 32 | | --------------- |----- | --- | 33 | | http_port | 8500 | HTTP APIのインターフェース | 34 | | http_port | 8501 | HTTPS APIのインターフェース| 35 | | grpc_port | 8502 | gRPC APIのインターフェース | 36 | | cluster_rpc_port | 8300 | サーバー間のRPC用 | 37 | | lan_serf | 8301 | LAN Serf | 38 | | lan_serf | 8302 | WAN Serf | 39 | ``` 40 | 41 |
42 | 43 | `main.tf`を以下のように編集してください。 44 | 45 |
46 | main.tf 47 | 48 | ```hcl 49 | resource "aws_security_group" "consul_security_group" { 50 | name = "consul_security_group" 51 | description = "Consul Sercuriy Group" 52 | vpc_id = var.vpc_id 53 | 54 | ingress { 55 | protocol = "tcp" 56 | cidr_blocks = var.cidr_blocks 57 | from_port = var.http_port 58 | to_port = var.http_port 59 | } 60 | 61 | ingress { 62 | cidr_blocks = var.cidr_blocks 63 | protocol = "tcp" 64 | from_port = var.https_port 65 | to_port = var.https_port 66 | } 67 | 68 | ingress { 69 | cidr_blocks = var.cidr_blocks 70 | protocol = "tcp" 71 | from_port = var.grpc_port 72 | to_port = var.grpc_port 73 | } 74 | 75 | ingress { 76 | cidr_blocks = var.cidr_blocks 77 | protocol = "tcp" 78 | from_port = var.cluster_rpc_port 79 | to_port = var.cluster_rpc_port 80 | } 81 | 82 | ingress { 83 | cidr_blocks = var.cidr_blocks 84 | protocol = "tcp" 85 | from_port = var.lan_serf 86 | to_port = var.lan_serf 87 | } 88 | 89 | egress { 90 | cidr_blocks = ["0.0.0.0/0"] 91 | protocol = "-1" 92 | from_port = 0 93 | to_port = 0 94 | } 95 | 96 | } 97 | ``` 98 |
99 | 100 | 101 | 次に以下のように`variables.tf`を編集します。 102 | 103 |
104 | variables.tf 105 | 106 | ```hcl 107 | variable "vpc_id" {} 108 | variable "http_port" { 109 | default = "8500" 110 | } 111 | variable "https_port" { 112 | default = "8501" 113 | } 114 | variable "grpc_port" { 115 | default = "8502" 116 | } 117 | variable "cluster_rpc_port" { 118 | default = "8600" 119 | } 120 | variable "lan_serf" { 121 | default = "8301" 122 | } 123 | variable "cidr_blocks" { 124 | type = list(string) 125 | default = ["0.0.0.0/0"] 126 | } 127 | ``` 128 |
129 | 130 | 最後に以下のように`output.tf`を編集します。 131 | 132 |
133 | output.tf 134 | 135 | ```hcl 136 | output "http_port" { 137 | value = var.http_port 138 | } 139 | 140 | output "https_port" { 141 | value = var.https_port 142 | } 143 | 144 | output "grpc_port" { 145 | value = var.grpc_port 146 | } 147 | 148 | output "cluster_rpc_port" { 149 | value = var.cluster_rpc_port 150 | } 151 | 152 | output "lan_serf" { 153 | value = var.lan_serf 154 | } 155 | ``` 156 |
157 | 158 | これでモジュール作りは終了です。GitHub にコミットしましょう。 159 | 160 | ```shell 161 | $ git init 162 | $ git add . 163 | $ git remote add origin https://github.com/tkaburagi/terraform-aws-securitygroup.git 164 | $ git commit -m "first commit" 165 | $ git tag -a 0.0.1 -m "v 0.0.1" 166 | $ git push origin 0.0.1 master 167 | ``` 168 | 169 | TFE の Private Module Registry のいいところはバージョン管理ができ、いつでも複数バージョンから選択できる点です。Git の tag からバージョンを取得しています。 170 | 171 | バージョニングは`x.x.x`のフォーマットでセマンティックバージョニングを行うのが良いでしょう。 172 | 173 | ## Module Registry の作成 174 | 175 | それではコミットしたモジュールを TFE 上で公開してみます。公開範囲は Organization で、その中の Workspace であればどのコンフィグからも利用可能です。 176 | 177 | 178 | 179 | 180 | 181 | トップ画面から`Modules`を選択します。(まだ何もリストされていないはずです) `Add Module`を選択し、 182 | 183 | 184 | 185 | 186 | 187 | 次の画面で先ほどのレポジトリを指定します。`Publish Module`を押してください。これでモジュールの公開は終了です。 188 | 189 | 190 | 191 | 192 | 193 | 上記のようなサマリー画面が見えるはずです。README、変数のインプットやバージョンなどの情報が確認出来ます。 194 | 195 | ## Module Registry の利用 196 | 197 | このモジュールを使ってみます。トップ画面から`Modules`に戻り、次は`Design configuration`をクリックします。 198 | 199 | `Design configuration`はモジュールを利用するための設定を行うためのエンタープライズ機能です。 200 | 201 | 先ほど追加したモジュールが出てくるはずなので`Add module`をクリックして、`Next`をクリックして下さい。 202 | 203 | なお、複数のモジュールを組み合わせて使う際はここから複数選択して、まとめてコンフィグレーションを行うことが出来ます。 204 | 205 | 206 | 207 | 208 | 209 | 変数の設定画面が出てくるはずです。`Design configuration` ではインプットに必要な変数を自動で検出して GUI 上で設定することが出来ます。この際、`variables.tf`で`default`が設定されているものは`OPTIONAL`、設定されていないものは`REQUIRED`となり、設定漏れを防ぐことが出来て便利です。 210 | 211 | 今回実際 Apply は行わないので適当な値を入れて`Next`をクリックして下さい。 212 | 213 | 214 | 215 | 216 | 217 | コードが生成されるはずです。これをコピーして既存のコードに利用してもいいですし、ダウンロードして新規のコンフィグレーションを立ち上げても OK です。このコードはここでしか取得出来ないので何らかの形で残しておきましょう。 218 | 219 | `Done`をクリックして終了します。 220 | 221 | 以上で一連の使い方は終了ですが、余力のある方はバージョンアップをやってみましょう。 222 | 223 | ## Module のバージョンアップ 224 | 225 | バージョンを指定して Module を利用出来ることはエンタープライズ版のメリットのうちの一つです。`main`, `variables`, `output`をそれぞれ以下のように編集します。 226 | 227 | `main.tf` 228 | 229 | ```hcl 230 | ingress { 231 | cidr_blocks = var.cidr_blocks 232 | protocol = "tcp" 233 | from_port = var.wan_serf 234 | to_port = var.wan_serf 235 | } 236 | ``` 237 | 238 | `variables.tf` 239 | 240 | ```hcl 241 | variable "wan_serf" { 242 | default = "8302" 243 | } 244 | ``` 245 | 246 | `output.tf` 247 | 248 | ```hcl 249 | output "wan_serf" { 250 | value = var.wan_serf 251 | } 252 | ``` 253 | 254 | コミットしてみましょう。 255 | 256 | ```shell 257 | $ git add . 258 | $ git commit -m "added wan feature" 259 | $ git tag -a 0.1.0 -m "v 0.1.0" 260 | $ git push origin 0.1.0 master 261 | ``` 262 | 263 | TFE の画面に戻り`Modules`をクリックしてモジュールの`Details`を選ぶとバージョンが`0.1.0`にアップしていることがわかるでしょう。 264 | 265 | また`Versions`のボタンから複数バージョンを確認することが出来ます。 266 | 267 | 268 | 269 | 270 | 271 | 実際に作成する際もこれらのバージョンから選択してモジュールを使っていくことになります。 272 | 273 | ## 参考リンク 274 | 275 | * [Terraform Registry](https://www.terraform.io/docs/registry/index.html) 276 | * [Private Module Registry](https://www.terraform.io/docs/cloud/registry/index.html) -------------------------------------------------------------------------------- /contents/tfc-remote-state.md: -------------------------------------------------------------------------------- 1 | # Terraform Cloud によるリモートステート管理 2 | 3 | さて、最初の Workshop では Local 環境で Terraform を実行し、State ファイルも Local 環境に作成されました。 4 | 5 | State ファイルは非常に重要なファイルで様々な情報がつまっています。 6 | - Provisioning された Resource の識別情報 7 | - API キーやパスワードなどの Secret 8 | - など 9 | 10 | Terraform で継続的に Provisioning を行なうためには State ファイルの管理が必至です。Terraform はデフォルトの挙動として、実行されて得た State ファイルを Local 環境に保存します。ただ、Local 環境で State ファイルを管理するにはいくつかの問題があります。 11 | - 個人の Local 環境だけに存在すると、チームでの作業が出来ない 12 | - 例えば A さんのローカルマシン上にだけ State ファイルがある場合、A さん以外の人はその環境にたいして、それ以上の Provisioning が出来ない。 13 | - 誤って削除してしまうと元に戻せない(よって全てのインフラ情報が損失してしまう) 14 | - 既存の環境を State ファイルに取り込む import というコマンドもありますが、非常に手間と時間がかかります。 15 | - State ファイルは常に最後の Terraform 実行の情報だけが記載されるので、過去のインフラ状態のトラッキングが出来ない 16 | - トラッキングのために、Terraform の実行毎に State ファイルを共有スペース(ファイルサーバーや S3 など)や VCS などに保存するやり方もありますが、手間がかかります。 17 | 18 | そこで、Terraform OSS のユーザーはこれらの問題を回避するために様々な仕組みをカスタムしてきました。ただ、これらのカスタマイズは各ユーザー側の開発・メンテナンスなどを必要とし、その管理のために本来の仕事とは別の時間を費やしてしまいます。 19 | 20 | Terraform Cloud 及び Terraform Enterprise にはこれらの問題を解決すべく、様々な Team collaboration 及び Governance の機能を予め用意してあります。これからの Workshop では、これら機能を紹介していきます。 21 | 22 | ## 事前準備 23 | 24 | 1. この Workshop を行なうには Terraform Cloud のアカウントが必要です。こちらからサインアップをしてください。(すでにアカウントをお持ちの方はスキップしてください。) 25 | 26 | [https://app.terraform.io/signup/account](https://app.terraform.io/signup/account) 27 | 28 | ## リモートステート管理機能 29 | 30 | Terraform Cloud には Remote State 管理機能があります。ちなみに、**この機能は誰でも無料で利用できます**。 31 | 32 | ここでは、Remote State 管理機能を使うエクササイズを行います。 33 | 34 | 35 | ### Organization の設定 36 | 37 | Terraform Cloud にログインし、新規 Organization を作成します。 38 | 39 | すでに Organization を作ってる人はこの手順をスキップしてください。 40 | Organization は、ユーザや、チームや、Workcpace を束ねて扱う事ができる最上位単位です。 41 | 42 | 43 | 44 | 45 | 46 | `+ create organization` ボタンを押して詳細を入力します。 47 | 48 | 49 | 50 | 51 | 52 | 53 | `Terraform organization name` には、unique な名前を設定します。 54 | 数字、文字、アンダースコア (_)、ハイフン (-)が利用できます。 55 | 56 | 入力したら、メールアドレスを入力して、 `create organization` ボタンを押したら Organization が作成されます。 57 | メールアドレスはログインした情報を元に自動で入力されています。 58 | 59 | 60 | 61 | 62 | 63 | ### Workspace の設定 64 | 65 | Organization を作成したら、新規 Workspace を作成します。 66 | ワークスペース名は任意で構いません。 67 | 68 | **1 つの Organization 内では全ての Workspace 名が一意である必要がありますので、複数のユーザーで作業する場合、Workspace 名がユニークになるようにしてください。** 69 | 70 | Workspace は以下の `Create a workspace` ボタンより作成できます。 71 | 72 | ![new workspace](../assets/tfc-remote-state/new_workspace.png) 73 | 74 | 以下の画面で、**CLI Drive Workflow**を選択してください。 75 | 76 | 77 | 78 | 79 | 80 | ここでは、ワークスペース名は**hello-tf**とします。 81 | 併せて、Workspace を作成する Project を選択します。 82 | 83 | Project を独自に作っている人は任意の Project を選択し、 84 | 初めて利用する人は最初から作られている **Default Project** のまま進んでください。 85 | 86 | **Create** ボタンを押すと Workspace が作成されます。 87 | 88 | 89 | 90 | 91 | 92 | つぎに作成したワークスペースの Settings メニューから以下の操作を実施して下さい。 93 | 94 | ``` 95 | * Settings > General ナビゲートし、Execution modeをLocalに設定 96 | * Save Settingsを選択して保存 97 | ``` 98 | 99 | 100 | 101 | 102 | 103 | Execution mode を**Local**に設定すると、Terraform の実行は Local 環境で行いますが、作成される State ファイルは Terraform Cloud に保存されます。 104 | 105 | ### User Token の作成 106 | 107 | さて、次に Local の Terraform 環境から Terraform Cloud にアクセスするために、User token を作成します。 108 | この User token はローカル環境や別のシステム(CI/CD パイプラインや外部ツールなど)から Terraform Cloud API を叩く際に必要となります。 109 | 110 | 右上の自分のアイコンをクリックして **Account settings** を選択します。 111 | 112 | 113 | 114 | 115 | 116 | そこから、**Tokens** メニューから **Create an API Token** ボタンで User Token を作成します。 117 | 118 | 119 | 120 | 121 | 122 | ダイアログが表示されるので、 123 | Description にはこの Token についての説明を追加します。 124 | ここでは **for workshop** などとしておくと workshop 用途で使った token である事を後から判別できます。 125 | 入力したら **Generate token** ボタンを押して Token を作成します。 126 | 127 | 128 | 129 | 130 | 131 | Tokens の一覧に、作成した Token が表示されます。 132 | 作成されたばかりの Token には、Token の文字列が表示されています。(キャプチャの灰色部分です) 133 | 作成された Token は画面遷移するともう表示されないので、必ず安全なところへコピーして控えておいてください。 134 | 135 | 136 | 137 | 138 | 139 | 140 | 次に、ここで作成された Token を Local 環境の設定ファイルに登録します。`terraform login` コマンドを使います。Token は `~/.terraform.d/credentials.tfrc.json `に保存されます。 141 | **Windows の場合、%APPDATA%\terraform.rc となります。** 142 | 143 | **Token for app.terraform.io:** と聞かれたら、Token をペーストします。 144 | 145 | ```console 146 | $ cd path/to/hello-tf 147 | $ terraform login 148 | Terraform will request an API token for app.terraform.io using your browser. 149 | 150 | If login is successful, Terraform will store the token in plain text in 151 | the following file for use by subsequent commands: 152 | /Users//.terraform.d/credentials.tfrc.json 153 | 154 | Do you want to proceed? 155 | Only 'yes' will be accepted to confirm. 156 | 157 | Enter a value: yes 158 | 159 | 160 | --------------------------------------------------------------------------------- 161 | 162 | Terraform must now open a web browser to the tokens page for app.terraform.io. 163 | 164 | If a browser does not open this automatically, open the following URL to proceed: 165 | https://app.terraform.io/app/settings/tokens?source=terraform-login 166 | 167 | 168 | --------------------------------------------------------------------------------- 169 | 170 | Generate a token using your browser, and copy-paste it into this prompt. 171 | 172 | Terraform will store the token in plain text in the following file 173 | for use by subsequent commands: 174 | /Users//.terraform.d/credentials.tfrc.json 175 | 176 | Token for app.terraform.io: 177 | Enter a value: 178 | 179 | 180 | Retrieved token for user 181 | ``` 182 | 183 | これで Local 環境から Terraform Cloud の API にアクセスする準備が整いました。 184 | 185 | ### Remote Backend の設定 186 | 187 | つぎに Terraform に Remote Backend を使用するコードを追加します。`main.tf` の `terraform` スタンザを以下のように変更してください。*YOUR_ORGANIZATION* は使用している Organization の値に置き換えてください。 188 | 189 | ```hcl 190 | terraform { 191 | #ここから 192 | cloud { 193 | organization = "YOUR_ORGANIZATION" 194 | 195 | workspaces { 196 | name = "hello-tf" 197 | } 198 | } 199 | #ここまで追加 200 | } 201 | ``` 202 | 203 | ここまでの準備が出来ましたら、Terraform を実行します。 204 | `terraform init` コマンドを実行してください。 205 | 206 | ```console 207 | % terraform init 208 | Initializing HCP Terraform... 209 | Migrating from backend "remote" to HCP Terraform. 210 | Initializing provider plugins... 211 | - Reusing previous version of hashicorp/aws from the dependency lock file 212 | - Using previously-installed hashicorp/aws v6.12.0 213 | 214 | HCP Terraform has been successfully initialized! 215 | 216 | You may now begin working with HCP Terraform. Try running "terraform plan" to 217 | see any changes that are required for your infrastructure. 218 | 219 | If you ever set or change modules or Terraform Settings, run "terraform init" 220 | again to reinitialize your working directory. 221 | ``` 222 | 223 | **Migrating from backend "remote" to HCP Terraform.** と表示されているのが確認できます。 224 | 225 | 実際に state ファイルを Terraform cloud の remote で管理するには `terraform apply` による実行が必要です。 226 | 227 | ```console 228 | $ terraform apply 229 | ``` 230 | 231 | この段階で、Terraform Cloud の Workspace を確認すると、State ファイルが作成されているはずです。 232 | 233 | 234 | 235 | 236 | 237 | 中を見てみると先ほど作成した環境のステートが記述されているはずです。 238 | 239 | 240 | 241 | 242 | 243 | ### 環境のクリーンアップ 244 | 245 | 次に`destroy`で環境をリセットします。 246 | 247 | ```shell 248 | $ terraform destroy 249 | ``` 250 | 実行中にステートレポジトリの GUI を見るとロックがかかっていることがわかります。 251 | 252 | 253 | 254 | 255 | 256 | 実行ししばらくすると EC2 インスタンスが `terminated` の状態になってることがわかるはずです。(GCP/Azure の場合は Web ブラウザから確認してください。) 257 | 258 | ```console 259 | $ aws ec2 describe-instances --query "Reservations[].Instances[].{InstanceId:InstanceId,State:State}" 260 | [ 261 | { 262 | "InstanceId": "i-0988a3fc4de8f2980", 263 | "State": { 264 | "Code": 48, 265 | "Name": "terminated" 266 | } 267 | }, 268 | { 269 | "InstanceId": "i-0b22f0bca411f88cb", 270 | "State": { 271 | "Code": 48, 272 | "Name": "terminated" 273 | } 274 | } 275 | ] 276 | ``` 277 | 278 | この `destroy` では Local の State ファイルではなく、Terraform Cloud 上の State ファイルを使用します。よって、もう Local の State ファイルは必要ないので削除しても構いません。 279 | 280 | 再度 Terraform Cloud の GUI からステートファイルを確認してください。変更が反映されることがわかるはずです。 281 | 282 | 283 | 284 | 285 | 286 | ## まとめ 287 | 288 | これで Remote Backend の設定は完了です。ここでのエクササイズでは、個人個人で Workspace を作りましたが、これをチームで共有することで State ファイルの共有が実現できます。 289 | 290 | ただ、State ファイルの共有が実現できたとしてもまだまだチーム利用としては足りない機能が多々あります。それらを次からの Workshop で見ていきたいと思います。 291 | -------------------------------------------------------------------------------- /contents/variables.md: -------------------------------------------------------------------------------- 1 | # Secure Variables Storage 2 | 3 | Terraform はソフトウェアの性質上、非常に機密性の高いデータを利用します。例えば AWS にプロビジョニングを実行する際は強い権限を持ったアカウントのキーを使う必要があります。 4 | 5 | 「初めての Terraform」の章で変数をセットするには下記の方法があると記述しました。 6 | 7 | * `tfvars`というファイルの中で定義する 8 | * `terraform apply -vars=***`という形で CLI の引数で定義する 9 | * `TF_VAR_***`という環境変数で定義する 10 | * Plan 中に対話式で入力して定義する 11 | 12 | いずれの方法もファイル、OS のコマンド履歴、環境変数などにシークレットが残ってしまう可能性があります。そこで Enterprise 版では HashiCorp Vault の暗号化エンジンをバックエンドで利用した Variable Storage を簡単に使うことができます。これによって安全に変数を扱うことができます。 13 | 14 | Variable のストレージは一つのワークスペースに一つ用意されます。またこの他にもステートファイルなど重要なデータは例外なく暗号化され保存されています。 15 | 16 | Terraform Enterprise には二種類に変数がサポートされています。`Terraform Variables`と`Environment Variables`です。 17 | 18 | `Terraform Variables`は Terrafrom で扱う変数です。ここでセットされた値は`terraform.tfvars`として扱われます。そのため連携する VCS にもし`terraform.tfvars`がある場合はオーバーライドされます。 19 | 20 | `Environment Variables`は Linux の Worker(Terraform Run などを行うコンポーネント)上にセットされる値です。ここでセットされた値が export コマンドよって実行前に環境変数としてセットされます。`Environment Variables`の中には TFE 上で特別な意味を持つものが存在します。 21 | 22 | * `CONFIRM_DESTROY`: `Destroy`を許容するかどうか。デフォルトでは無効です。 23 | * `TFE_PARALLELISM`: 処理高速化のための並列実行のオプション。デフォルトは 10 です。 24 | 25 | 26 | ## 変数のセット 27 | 28 | 先ほど作成した`tf-handson-workshop`に変更を加えます。 29 | 30 | ```shell 31 | $ cd path/to/tf-workspace/tf-handson-workshop 32 | ``` 33 | 34 | 以下の二つのファイルを作成してください。`main.tf`, `variables.tf` というファイル名です。 35 | 36 | 37 | ```shell 38 | $ cat < main.tf 39 | 40 | terraform { 41 | } 42 | 43 | provider "aws" { 44 | access_key = var.access_key 45 | secret_key = var.secret_key 46 | region = var.region 47 | } 48 | 49 | resource "aws_instance" "hello-tf-instance" { 50 | ami = var.ami 51 | count = var.hello_tf_instance_count 52 | instance_type = var.hello_tf_instance_type 53 | } 54 | 55 | EOF 56 | ``` 57 | 58 | ```shell 59 | $ cat << EOF > variables.tf 60 | 61 | variable "access_key" {} 62 | variable "secret_key" {} 63 | variable "region" {} 64 | variable "ami" {} 65 | variable "hello_tf_instance_count" { 66 | default = 2 67 | } 68 | variable "hello_tf_instance_type" { 69 | default = "t2.micro" 70 | } 71 | 72 | EOF 73 | ``` 74 | 75 |
GCPの場合はこちら 76 | 77 | ```hcl 78 | terraform { 79 | } 80 | 81 | provider "google" { 82 | credentials = var.gcp_key 83 | project = var.project 84 | region = var.region 85 | } 86 | 87 | resource "google_compute_instance" "vm_instance" { 88 | name = "terraform-instance-${count.index}-" 89 | count = var.hello_tf_instance_count 90 | machine_type = var.machine_type 91 | zone = "asia-northeast1-a" 92 | boot_disk { 93 | initialize_params { 94 | image = var.image 95 | } 96 | } 97 | 98 | network_interface { 99 | # A default network is created for all GCP projects 100 | network = "default" 101 | access_config { 102 | } 103 | } 104 | } 105 | ``` 106 | ```hcl 107 | variable "gcp_key" {} 108 | variable "machine_type" {} 109 | variable "hello_tf_instance_count" { 110 | default = 1 111 | } 112 | variable "region" { 113 | default = "asia-northeast1" 114 | } 115 | variable "project" {} 116 | variable "image" {} 117 | ``` 118 |
119 | 120 | 121 |
Azureの場合はこちら 122 | 123 | ```hcl 124 | terraform { 125 | } 126 | 127 | provider "azurerm" { 128 | client_id = var.client_id 129 | tenant_id = var.tenant_id 130 | subscription_id = var.subscription_id 131 | client_secret = var.client_secret 132 | features {} 133 | } 134 | 135 | resource "azurerm_virtual_machine" "main" { 136 | name = "my-vm-${count.index}" 137 | count = var.hello_tf_instance_count 138 | location = var.location 139 | resource_group_name = azurerm_resource_group.example.name 140 | network_interface_ids = [azurerm_network_interface.example.*.id[count.index]] 141 | vm_size = var.vm_size 142 | 143 | os_profile { 144 | computer_name = "hostname" 145 | admin_username = "vmadmin" 146 | admin_password = var.admin_password 147 | } 148 | os_profile_linux_config { 149 | disable_password_authentication = false 150 | } 151 | storage_image_reference { 152 | publisher = "Canonical" 153 | offer = "UbuntuServer" 154 | sku = "16.04-LTS" 155 | version = "latest" 156 | } 157 | storage_os_disk { 158 | name = "my-osdisk-${count.index}" 159 | caching = "ReadWrite" 160 | create_option = "FromImage" 161 | managed_disk_type = "Standard_LRS" 162 | } 163 | } 164 | 165 | resource "azurerm_resource_group" "example" { 166 | name = "my-group" 167 | location = var.location 168 | } 169 | 170 | 171 | resource "azurerm_virtual_network" "example" { 172 | name = "my-network" 173 | address_space = ["10.0.0.0/16"] 174 | location = var.location 175 | resource_group_name = azurerm_resource_group.example.name 176 | } 177 | 178 | resource "azurerm_subnet" "example" { 179 | name = "my-subnet" 180 | resource_group_name = azurerm_resource_group.example.name 181 | virtual_network_name = azurerm_virtual_network.example.name 182 | address_prefix = "10.0.2.0/24" 183 | } 184 | 185 | resource "azurerm_network_interface" "example" { 186 | name = "my-nw-interface-${count.index}" 187 | count = var.hello_tf_instance_count 188 | location = var.location 189 | resource_group_name = azurerm_resource_group.example.name 190 | 191 | ip_configuration { 192 | name = "my-ip-config" 193 | subnet_id = azurerm_subnet.example.id 194 | private_ip_address_allocation = "Dynamic" 195 | } 196 | 197 | tags = { 198 | environment = "payground" 199 | } 200 | } 201 | ``` 202 | ```hcl 203 | variable "vm_size" {} 204 | variable "client_id" {} 205 | variable "client_secret" {} 206 | variable "tenant_id" {} 207 | variable "subscription_id" {} 208 | variable "location" {} 209 | variable "admin_password" {} 210 | variable "hello_tf_instance_count" { 211 | default = 1 212 | } 213 | ``` 214 |
215 | このようになっていればOKです。 216 | 217 | ``` 218 | . 219 | └── tf-handson-workshop 220 | ├── main.tf 221 | └── variables.tf 222 | ``` 223 | 224 | `hello-tf`では環境変数を使って変数の値をセットしましたが、今回はエンタープライズの機能を利用します。変数のセットは GUI もしくは CLI で設定できます。 225 | 226 | 227 | 228 | 229 | 230 | `Add variables`をクリックして以下の変数を入力してください。 231 | 232 | ``` 233 | * access_key : 自身のキー : Senstive 234 | * secret_key : 自身のキー : Senstive 235 | * region : ap-northeast-1 236 | * ami : ami-06d9ad3f86032262d 237 | * hello_tf_instance_count : 1 238 | * hello_tf_instance_type : t2.micro 239 | ``` 240 | 241 |
GCPの場合はこちら 242 | 243 | ``` 244 | * gcp_key : JSONファイルコピペ : Senstive 245 | * region : ap-northeast1 246 | * image : debian-cloud/debian-9 247 | * machine_type : f1-micro 248 | * project : PROJECT_NAME 249 | * hello_tf_instance_count : 1 250 | ``` 251 |
252 | 253 |
Azureの場合はこちら 254 | 255 | ``` 256 | * client_id : ******* : Senstive 257 | * client_secret : ******* : Senstive 258 | * tenant_id : ******* : Senstive 259 | * subscription_id : ******* : Senstive 260 | * location : East Asia 261 | * admin_password : Password1234! 262 | * hello_tf_instance_count : 1 263 | * vm_size : Standard_DS1_v2 264 | ``` 265 |
266 | 267 | 268 | 269 | 270 | 271 | またこれらの値は[TFC API](https://www.terraform.io/docs/cloud/api/variables.html)でセットすることもできます。 272 | 273 | ここまで完了したらコードをコミットしてみます。 274 | 275 | ```shell 276 | $ git add . 277 | $ git commit -m "second commit" 278 | $ git push -u origin main 279 | ``` 280 | 281 | ## 実行結果の確認 282 | 283 | ワークスペースのトップ画面に戻り`Runs`のタブを見ると新規のコミットに対してプランが走っていることがわかります。 284 | 285 | 286 | 287 | 288 | 289 | クリックしてプランの詳細を確認し、プランが終わると Apply 可能になります。`Confirm`をクリックし Apply を実行ししばらくすると成功するはずです。 290 | 291 | AWS CLI で確認します。(GCP/Azure の場合は Web ブラウザから確認してください。) 292 | 293 | ```console 294 | $ aws ec2 describe-instances --query "Reservations[].Instances[].{InstanceId:InstanceId,State:State}" 295 | [ 296 | { 297 | "InstanceId": "i-07bbdb6c0532e1617", 298 | "State": { 299 | "Code": 16, 300 | "Name": "running" 301 | } 302 | } 303 | ] 304 | ``` 305 | 306 | 節約のために環境を綺麗にしておきましょう。ワークスペースのセッテイングから`Destruction and Deletion`を選びます。 307 | 308 | 309 | 310 | 311 | 312 | `Queue Destroy Plan`を選択し、プランが完了したら`Confirm & Apply`で destroy を実行してください。 313 | 314 | 315 | 316 | 317 | 318 | Destroy されていることを確認しましょう。(GCP/Azure の場合は Web ブラウザから確認してください。) 319 | 320 | ```console 321 | $ aws ec2 describe-instances --query "Reservations[].Instances[].{InstanceId:InstanceId,State:State}" 322 | [ 323 | { 324 | "InstanceId": "i-07bbdb6c0532e1617", 325 | "State": { 326 | "Code": 16, 327 | "Name": "terminated" 328 | } 329 | } 330 | ] 331 | ``` 332 | 333 | ## ステートファイルの確認 334 | 335 | ユーザは Terraform のステートファイルの運用を TFC に任せることができます。TFC はステートファイルのシングルレポジトリとなり、ステートの共有、外部ストレージの作成、外部ストレージへの保存、暗号化やバージョン管理などといった通常必要な作業を実施してくれます。 336 | 337 | ワークスペースのメニューから`States`を選択します。 338 | 339 | 340 | 341 | 342 | 343 | 複数バージョンのステートがリストされていることがわかります。最新のステートを選択してみましょう。 344 | 345 | 346 | 347 | 348 | 349 | diff が取られ、変更点なども直感的に確認できます。各実行は毎回この最新のステートが取得されるためステートファイルの共有などもシンプルなワークフローとなります。 350 |   351 | ## 参考リンク 352 | * [Data Protection](https://www.terraform.io/docs/enterprise/system-overview/data-security.html) 353 | * [Variables](https://www.terraform.io/docs/cloud/workspaces/variables.html) 354 | * [TFC API Doc](https://www.terraform.io/docs/cloud/api/index.html) 355 | * [Terraform Variables と Environ Variables](https://www.terraform.io/docs/cloud/workspaces/variables.html#terraform-variables) 356 | -------------------------------------------------------------------------------- /contents/hello-terraform.md: -------------------------------------------------------------------------------- 1 | # 初めての Terraform 2 | 3 | ここでは OSS 版の Terraform を利用して AWS 上に一つインスタンスを作り、それぞれのコンポーネントや用語について説明をします。 4 | 5 | Terrform がインストールされていない場合は[こちら](https://www.terraform.io/downloads.html)よりダウンロードをしてください。 6 | 7 | ダウンロードしたら unzip して実行権限を付与し、パスを通します。下記は macOS の手順です。 8 | 9 | ```console 10 | $ unzip terraform*.zip 11 | $ chmod + x terraform 12 | $ mv terraform /usr/local/bin 13 | $ terraform -version 14 | Terraform v0.12.6 15 | ``` 16 | 17 | 次に任意の作業用ディレクトリを作ります。 18 | 19 | ```shell 20 | $ mkdir -p tf-workspace/hello-tf 21 | $ cd tf-workspace/hello-tf 22 | ``` 23 | 24 | 早速このフォルダに Terraform のコンフィグファイルを作ってみます。コンフィグファイルは`HashiCorp Configuration Language`というフレームワークを使って記述していきます。 25 | 26 | `main.tf`と`vaiables.tf`という二つのファイルを作ってみます。`main.tf`はその名の通り Terraform のメインのファイルで、このファイルに記述されている内容が Terraform で実行されます。`variables.tf`は変数を定義するファイルです。各変数にはデフォルト値や型などを指定できます。 27 | 28 | ```shell 29 | $ cat < main.tf 30 | terraform { 31 | } 32 | 33 | provider "aws" { 34 | access_key = var.access_key 35 | secret_key = var.secret_key 36 | region = var.region 37 | } 38 | 39 | resource "aws_instance" "hello-tf-instance" { 40 | ami = var.ami 41 | count = var.hello_tf_instance_count 42 | instance_type = var.hello_tf_instance_type 43 | } 44 | 45 | EOF 46 | ``` 47 | 48 |
GCPの場合はこちら 49 | 50 | ```hcl 51 | terraform { 52 | } 53 | 54 | provider "google" { 55 | credentials = var.gcp_key 56 | project = var.project 57 | region = var.region 58 | } 59 | 60 | resource "google_compute_instance" "vm_instance" { 61 | name = "terraform-instance-${count.index}-" 62 | count = var.hello_tf_instance_count 63 | machine_type = var.machine_type 64 | zone = "asia-northeast1-a" 65 | boot_disk { 66 | initialize_params { 67 | image = var.image 68 | } 69 | } 70 | 71 | network_interface { 72 | # A default network is created for all GCP projects 73 | network = "default" 74 | access_config { 75 | } 76 | } 77 | } 78 | ``` 79 |
80 | 81 |
Azureの場合はこちら 82 | 83 | ```hcl 84 | terraform { 85 | } 86 | 87 | provider "azurerm" { 88 | client_id = var.client_id 89 | tenant_id = var.tenant_id 90 | subscription_id = var.subscription_id 91 | client_secret = var.client_secret 92 | features {} 93 | } 94 | 95 | resource "azurerm_virtual_machine" "main" { 96 | name = "my-vm-${count.index}" 97 | count = var.hello_tf_instance_count 98 | location = var.location 99 | resource_group_name = azurerm_resource_group.example.name 100 | network_interface_ids = [azurerm_network_interface.example.*.id[count.index]] 101 | vm_size = "Standard_DS1_v2" 102 | 103 | os_profile { 104 | computer_name = "hostname" 105 | admin_username = "vmadmin" 106 | admin_password = var.admin_password 107 | } 108 | os_profile_linux_config { 109 | disable_password_authentication = false 110 | } 111 | storage_image_reference { 112 | publisher = "Canonical" 113 | offer = "UbuntuServer" 114 | sku = "16.04-LTS" 115 | version = "latest" 116 | } 117 | storage_os_disk { 118 | name = "my-osdisk-${count.index}" 119 | caching = "ReadWrite" 120 | create_option = "FromImage" 121 | managed_disk_type = "Standard_LRS" 122 | } 123 | } 124 | 125 | resource "azurerm_resource_group" "example" { 126 | name = "my-group" 127 | location = var.location 128 | } 129 | 130 | 131 | resource "azurerm_virtual_network" "example" { 132 | name = "my-network" 133 | address_space = ["10.0.0.0/16"] 134 | location = var.location 135 | resource_group_name = azurerm_resource_group.example.name 136 | } 137 | 138 | resource "azurerm_subnet" "example" { 139 | name = "my-subnet" 140 | resource_group_name = azurerm_resource_group.example.name 141 | virtual_network_name = azurerm_virtual_network.example.name 142 | address_prefix = "10.0.2.0/24" 143 | } 144 | 145 | resource "azurerm_network_interface" "example" { 146 | name = "my-nw-interface-${count.index}" 147 | count = var.hello_tf_instance_count 148 | location = var.location 149 | resource_group_name = azurerm_resource_group.example.name 150 | 151 | ip_configuration { 152 | name = "my-ip-config" 153 | subnet_id = azurerm_subnet.example.id 154 | private_ip_address_allocation = "Dynamic" 155 | } 156 | 157 | tags = { 158 | environment = "payground" 159 | } 160 | } 161 | ``` 162 |
163 | 164 | 165 | 次に`variables.tf`ファイルを作ります。 166 | 167 | ```shell 168 | $ cat << EOF > variables.tf 169 | variable "access_key" {} 170 | variable "secret_key" {} 171 | variable "region" {} 172 | variable "ami" {} 173 | variable "hello_tf_instance_count" { 174 | default = 1 175 | } 176 | variable "hello_tf_instance_type" { 177 | default = "t2.micro" 178 | } 179 | EOF 180 | ``` 181 | 182 |
GCPの場合はこちら 183 | 184 | ```hcl 185 | variable "gcp_key" {} 186 | variable "machine_type" {} 187 | variable "hello_tf_instance_count" { 188 | default = 1 189 | } 190 | variable "region" { 191 | default = "asia-northeast1" 192 | } 193 | variable "project" {} 194 | variable "image" {} 195 | ``` 196 |
197 | 198 |
Azureの場合はこちら 199 | 200 | ```hcl 201 | variable "client_id" {} 202 | variable "client_secret" {} 203 | variable "tenant_id" {} 204 | variable "subscription_id" {} 205 | variable "location" {} 206 | variable "admin_password" {} 207 | variable "hello_tf_instance_count" { 208 | default = 1 209 | } 210 | ``` 211 |
212 | 213 | 二つのファイルができたらそのディレクトリ上で Terraform の初期化処理を行います。`init`処理ではステートファイルの保存先などのバックエンドの設定や必要ばプラグインのインストールを実施します。 214 | 215 | ```shell 216 | $ terraform init 217 | ``` 218 | 219 | ここでは AWS(or GCP or Azure)のプラグインがインストールされるはずです。 220 | 221 | ```console 222 | $ ls -R .terraform/providers #OR ls -R .terraform/plugins 223 | darwin_amd64 224 | 225 | .terraform/plugins/darwin_amd64: 226 | lock.json terraform-provider-aws_v2.24.0_x4 227 | ``` 228 | 229 | 次に`plan`と`apply`を実施してインスタンスを作ってみましょう。aws cli でインスタンスの状況確認しておいてください。(GCP/Azure の場合は Web ブラウザから確認してください。) 230 | 231 | ```console 232 | $ aws ec2 describe-instances --query "Reservations[].Instances[].{InstanceId:InstanceId,State:State}" 233 | [ 234 | 235 | ] 236 | ``` 237 | 238 | >aws cli にログイン出来ていない場合、以下のコマンドでログインしてください。 239 | > 240 | >```console 241 | >$ aws configure 242 | >AWS Access Key ID [****************]: **************** 243 | >AWS Secret Access Key [****************]: **************** 244 | >Default region name [ap-northeast-1]: 245 | >Default output format [json]: 246 | >``` 247 | 248 | `plan`は Terraform によるプロビジョニングの実行プランを計画します。実際の環境やステートファイルとの差分を検出し、どのリソースにどのような変更を行うかを確認することができます。`apply`はプランに基づいたプロビジョニングの実施をするためのコマンドです。 249 | 250 | また、実行前に変数に値をセットする必要があります。方法としては 251 | 252 | * `tfvars`というファイルの中で定義する 253 | * `terraform apply -vars=***`という形で CLI の引数で定義する 254 | * `TF_VAR_***`という環境変数で定義する 255 | * Plan 中に対話式で入力して定義する 256 | 257 | がありますが、今回は環境変数でセットします。 258 | 259 | ```shell 260 | $ export TF_VAR_access_key=************ 261 | $ export TF_VAR_secret_key=************ 262 | $ export TF_VAR_region=ap-northeast-1 263 | $ export TF_VAR_ami=ami-06d9ad3f86032262d 264 | $ terraform plan 265 | $ terraform apply 266 | ``` 267 | 268 |
GCPの場合はこちら 269 | 270 | ``` 271 | $ export TF_VAR_gcp_key=PATH_TO_KEY_JSON 272 | $ export TF_VAR_machine_type=f1-micro 273 | $ export TF_VAR_image=debian-cloud/debian-9 274 | $ export TF_VAR_project=YOUT_PROJECT 275 | $ terraform plan 276 | $ terraform apply 277 | ``` 278 |
279 | 280 |
Azureの場合はこちら 281 | 282 | ``` 283 | $ export TF_VAR_client_id="************" 284 | $ export TF_VAR_subscription_id="************" 285 | $ export TF_VAR_client_secret="************" 286 | $ export TF_VAR_tenant_id="************" 287 | $ export TF_VAR_location="East Asia" 288 | $ export TF_VAR_admin_password="Password1234" 289 | $ terraform plan 290 | $ terraform apply 291 | ``` 292 |
293 | 294 | Apply が終了すると AWS(or GCP or Azure)のインスタンスが一つ作られていることがわかるでしょう。(GCP/Azure の場合は Web ブラウザから確認してください。) 295 | 296 | ```console 297 | $ aws ec2 describe-instances --query "Reservations[].Instances[].{InstanceId:InstanceId,State:State}" 298 | [ 299 | { 300 | "InstanceId": "i-00918d5c9466da418", 301 | "State": { 302 | "Code": 48, 303 | "Name": "running" 304 | } 305 | } 306 | ] 307 | ``` 308 | 309 | 次にインスタンスの数を増やしてみます。`hello_tf_instance_count`の値を上書きして再度実行します。 310 | 311 | ```shell 312 | $ export TF_VAR_hello_tf_instance_count=2 313 | $ terraform plan 314 | $ terraform apply -auto-approve 315 | ``` 316 | 317 | ちなみに今回は`-auto-approve`というパラメータを使って途中の実行確認を省略しています。AWS(or GCP or Azure)のインスタンスが二つに増えています。Terraform は環境に差分が生じた際は Plan で差分を検出し、差分のみ実施するため既存のリソースには何の影響も及ぼしません。(GCP/Azure の場合は Web ブラウザから確認してください。) 318 | 319 | ```console 320 | $ aws ec2 describe-instances --query "Reservations[].Instances[].{InstanceId:InstanceId,State:State}" 321 | [ 322 | { 323 | "InstanceId": "i-00918d5c9466da418", 324 | "State": { 325 | "Code": 48, 326 | "Name": "running" 327 | } 328 | }, 329 | { 330 | "InstanceId": "i-0b0aea4b4ab27ef4b", 331 | "State": { 332 | "Code": 16, 333 | "Name": "running" 334 | } 335 | } 336 | ] 337 | ``` 338 | 339 | ## Web システムをプロビジョニングする 340 | 341 | 次はもう少し複雑な構成のシステムをプロジョニングしてみましょう。 342 | 343 | ```shell 344 | $ cd .. 345 | $ git clone https://github.com/tkaburagi/tf-simple-web 346 | $ cd tf-simple-web 347 | ``` 348 | 349 |
Azureの場合はこちら 350 | 351 | ```shell 352 | $ cd .. 353 | $ git clone https://github.com/tkaburagi/tf-azure-simple-web 354 | $ cd tf-azure-simple-web 355 | ``` 356 |
357 | 358 | 二つの AWS インスタンスを立ち上げ、その上に Apache をインストールしその二つのインスタンスをインスタンスグループとして ALB にアタッチしています。そのために必要な最低限のネットワーク設定も行なっていますので気になる人はコードを見てみてください。 359 | 360 | Azure でチームで同じ環境を利用している場合は`main.tf`の`azurerm_resource_group` -> `name`を`my-group-kabu`のような形に変更してください。 361 | 362 | Terraform Apply してみましょう。 363 | 364 | ```shell 365 | $ terraform init 366 | $ terraform plan 367 | $ terraform apply 368 | ``` 369 | 370 | Apply が成功するとアウトプットとして以下のような内容が出力されるはずです。(Azure の場合は Public IP が出力されます。) 371 | 372 | ``` 373 | Outputs: 374 | 375 | alb_dns = web-alb-1553156387.ap-northeast-1.elb.amazonaws.com 376 | ``` 377 | 378 | こちらに Web ブラウザでアクセスして、Apache が起動していることを確認してみましょう。 379 | また、AWS のコンソールを確認してインスタンスや LB の他に VPC, Security Group も作られていることを確認してみましょう。 380 | 381 | destroy で環境をリセットします。 382 | 383 | ```shell 384 | $ terraform destroy -auto-approve 385 | ``` 386 | 387 | ## Enterprise 版の価値 388 | 389 | Apply が実行されると`terraform.tfstate`というファイルが生成されます。このファイルは現在のインフラの状態を Json 形式で保持しているものですが、次の Plan のタイミングの差分の検出などで扱われ非常に重要です。例えばチームで作業をする際などはこのステートの共有方法をどうやって運用するかなどの考慮が必要になります。 390 | 391 | また、このファイルには各リソースの ID のみならずデータベースや AWS 環境のシークレットなど様々な機密性の高いデータが含まれておりステートファイルをセキュアに保つことも運用上重要です。 392 | 393 | 以降の章ではステートファイルのみならず、OSS 版では Terraform を安全に利用するために考慮する必要がある様々な運用上の課題に対して Enterprise 版がどのような機能を提供しているかを一つずつ試してみます。 394 | 395 | 396 | ## 参考リンク 397 | * [State](https://www.terraform.io/docs/state/index.html) 398 | * [Backends](https://www.terraform.io/docs/backends/index.html) 399 | * [init](https://www.terraform.io/docs/commands/init.html) 400 | * [plan](https://www.terraform.io/docs/commands/plan.html) 401 | * [apply](https://www.terraform.io/docs/commands/apply.html) 402 | * [AWS Provider](https://www.terraform.io/docs/providers/aws/index.html) 403 | -------------------------------------------------------------------------------- /contents/api-driven-run.md: -------------------------------------------------------------------------------- 1 | # API Drive Runs 2 | 3 | TFC (*Terraform cloud*) 及び TFE (*Terraform Enterprise*)では、3 つの Apply の方法があります。 4 | 5 | - CLI Driven 6 | - VCS Driven 7 | - API Driven 8 | 9 | ここでは API Driven のやり方を試していきます。 10 | API Driven を用いることで、CI ツールやカスタムのシステムなどで管理される Terraform のコードを API 呼び出しのみで実行することができます。 11 | 12 | 13 | ## 事前準備 14 | 15 | ここでの作業を行う前に以下のものを準備ください。 16 | 17 | - [TFC の**API Token**](https://www.terraform.io/docs/cloud/users-teams-organizations/api-tokens.html) 18 | - User API token もしくは Team API token を発行してください。 19 | - Plan, Apply, upload states の権限が必用なので、対象となる Workspace へ Write が許可されていることを確認してください。 20 | - TFC の**Organization**名 21 | - TFC の**Workspace**名 22 | - TFC の**Workspace**の ID 23 | - Workspace ID は TFC の UI もしくは API から取得できます。 24 | - UI の場合 25 | - Workspace → Settings → General → ID 26 | - API の場合 27 | ```shell 28 | export ORGANIZATION= 29 | export WORKSPACE_NAME= 30 | export TOKEN= 31 | 32 | curl --header "Authorization: Bearer ${TOKEN}" --header "Content-Type: application/vnd.api+json" https://app.terraform.io/api/v2/organizations/${ORGANIZATION}/workspaces/${WORKSPACE_NAME} | jq -r '.data.id' 33 | ``` 34 | 35 | ## 大まかな流れ 36 | 37 | API による Run の実行は以下の流れで行われます。 38 | 39 | 1. Configuration version の作成 40 | 2. Terraform のコードを Configuration version へアップロード 41 | 3. Run に対して Apply を作成 42 | - **注意** Workspace の Apply Method が`Auto apply`に設定されている場合は自動的に Apply が作成されます。 43 | 44 | * 詳細については[こちら](https://www.terraform.io/docs/cloud/run/api.html)を参照ください。 45 | 46 | 47 | ## Configuration version の作成 48 | 49 | [Configurationo version](https://www.terraform.io/docs/cloud/api/configuration-versions.html#create-a-configuration-version)はアップロードする Terraform コードへの参照用リソースです。Configuration version を作成すると、Terraform コードをアップロードする先の URL が取得できます。 50 | 51 | Workspace に対して Configuration version を作成します。この例では、`data.attributes.auto-queue-runs`に`true`を指定しています。この設定では Terraform コードがアップロードされると自動的に Plan&Apply が実行されます。 52 | 53 | 54 | ```shell 55 | export WORKSPACE_ID= 56 | 57 | cat << EOF > configuration_version.json 58 | { 59 | "data": { 60 | "type": "configuration-versions", 61 | "attributes": { 62 | "auto-queue-runs": true, 63 | "speculative": false 64 | } 65 | } 66 | } 67 | EOF 68 | 69 | curl --header "Authorization: Bearer ${TOKEN}" --header "Content-Type: application/vnd.api+json" --request POST --data @configuration_version.json https://app.terraform.io/api/v2/workspaces/${WORKSPACE_ID}/configuration-versions 70 | ``` 71 | 72 | 成功すると以下のようなレスポンスが返ってきます。 73 | 74 | ```json 75 | { 76 | "data": { 77 | "id": "cv-RopxxxXWJR1QLq8D", 78 | "type": "configuration-versions", 79 | "attributes": { 80 | "auto-queue-runs": true, 81 | "error": null, 82 | "error-message": null, 83 | "source": "tfe-api", 84 | "status": "pending", 85 | "status-timestamps": {}, 86 | "changed-files": [], 87 | "upload-url": "https://archivist.terraform.io/v1/object/dmF1bHQ6djE6MVZRc0JpbVY2b0tuV0dydmVXSVVyRzJ2VEZuSmdBRmo2QWM5TmNGdFRVK29tTHRKdU9CdGJsWjNablR0ZWsrQVEvQkxHbHFnY3lRVUJ1NEt4dHhnWjVRN29BVXQrL0w1L0Y1eE1IeFhtY3hZUkRMaFYvUW1QUG51MzVkeUt4eDZ2U3VQc09jVXlWQ1YrZ0c1WHRzUTR1M0hJRU4rZkRna1k0WGJqaCt0ZFhFRTdaS3EyREJnTzI0YkFyQ0FqbFNzdTg5QnhPTVFFdWRsei95N2NlaERvUkxQY0dacVBEN25KOXFkbFRQeUxLV2hPNWp1ajJvaG1CRlVQZmJZZzR4cHlLc25hOGFZbGFBSWgyMFVNSzRPTGtvZkpkRGhzdTg9" 88 | }, 89 | "relationships": { 90 | "ingress-attributes": { 91 | "data": null, 92 | "links": { 93 | "related": "/api/v2/configuration-versions/cv-RopxxxXWJR1QLq8D/ingress-attributes" 94 | } 95 | } 96 | }, 97 | "links": { 98 | "self": "/api/v2/configuration-versions/cv-RopxxxXWJR1QLq8D" 99 | } 100 | } 101 | } 102 | ``` 103 | 104 | このレスポンスの`.data.attributes.upload-url`が Terraform コードをアップロードする先になります。 105 | 106 | ## Terraform のコードを Configuration version へアップロード 107 | 108 | 次に Terraform のコード群をアップロードするために`tar.gz`フォーマットへパッケージングします。この章の目的は Provisioning ではなく API Driven の動作方法なので、ここでは簡単に実行できるシンプルな Terraform コードを使います。 109 | 110 | ```shell 111 | mkdir tf_test 112 | cat << EOF > tf_test/main.tf 113 | resource "random_pet" "pet" { 114 | keepers = { 115 | val = timestamp() 116 | } 117 | } 118 | 119 | output "pet" { 120 | value = random_pet.pet.id 121 | } 122 | EOF 123 | 124 | tar cvfz main.tar.gz -C tf_test . 125 | ``` 126 | 127 | >**Note** 128 | > 129 | >tar.gz パッケージのルートディレクトリが、そのまま Terraform 実行時のディレクトリになります。以下のような構成になっていれば OK です。 130 | ```shell 131 | $ tar tvfz main.tar.gz 132 | drwxr-xr-x 0 masa staff 0 4 14 14:59 ./ 133 | -rw-r--r-- 0 masa staff 130 4 14 14:59 ./main.tf 134 | ``` 135 | 136 | それでは[アップロード用の API](https://www.terraform.io/docs/cloud/api/configuration-versions.html#upload-configuration-files)を使ってパッケージをアップロードします。 137 | 138 | ```shell 139 | curl --header "Authorization: Bearer ${TOKEN}" --header "Content-Type: application/vnd.api+json" --request PUT --data-binary @main.tar.gz https://archivist.terraform.io/v1/object/dmF1bHQ6djE6MVZRc0JpbVY2b0tuV0dydmVXSVVyRzJ2VEZuSmdBRmo2QWM5TmNGdFRVK29tTHRKdU9CdGJsWjNablR0ZWsrQVEvQkxHbHFnY3lRVUJ1NEt4dHhnWjVRN29BVXQrL0w1L0Y1eE1IeFhtY3hZUkRMaFYvUW1QUG51MzVkeUt4eDZ2U3VQc09jVXlWQ1YrZ0c1WHRzUTR1M0hJRU4rZkRna1k0WGJqaCt0ZFhFRTdaS3EyREJnTzI0YkFyQ0FqbFNzdTg5QnhPTVFFdWRsei95N2NlaERvUkxQY0dacVBEN25KOXFkbFRQeUxLV2hPNWp1ajJvaG1CRlVQZmJZZzR4cHlLc25hOGFZbGFBSWgyMFVNSzRPTGtvZkpkRGhzdTg9 140 | ``` 141 | 142 | この API が成功すると TFC 上の Workspace で Plan&Apply が実行されます。 143 | 144 | ## Run に対して Apply を作成 145 | 146 | > **Note** 147 | > 148 | > Workspace の Apply Method が`Auto apply`に設定されている場合は自動的に Apply が作成されますので、このセクションはスキップできます。 149 | 150 | Workspace の Apply Method が`Manual apply`に設定されている場合、実際の Apply を実行する際に**Confirm apply**の承認をする必要があります。ここでは、その承認を API で行なうやり方を試します。 151 | 152 | まずは Workspace から Apply したい Run の ID を取得します。[List Runs in a Worrkspace](https://www.terraform.io/docs/cloud/api/run.html#list-runs-in-a-workspace)の API を使用します。 153 | 154 | ```shell 155 | export WORKSPACE_ID= 156 | 157 | curl --header "Authorization: Bearer ${TOKEN}" --header "Content-Type: application/vnd.api+json" https://app.terraform.io/api/v2/workspaces/${WORKSPACE_ID}/runs?page%5Bsize%5D=1 158 | ``` 159 | 160 | 成功すると以下のようなレスポンスが返ってきます。 161 | 162 |
List Runのレスポンス 163 | 164 | ```json 165 | { 166 | "data": [ 167 | { 168 | "id": "run-bc4WDCExqr5CF4Ad", 169 | "type": "runs", 170 | "attributes": { 171 | "actions": { 172 | "is-cancelable": false, 173 | "is-confirmable": true, 174 | "is-discardable": true, 175 | "is-force-cancelable": false 176 | }, 177 | "canceled-at": null, 178 | "created-at": "2020-04-14T06:34:05.962Z", 179 | "has-changes": true, 180 | "is-destroy": false, 181 | "message": "New configuration uploaded via the Terraform Cloud API", 182 | "plan-only": false, 183 | "source": "tfe-configuration-version", 184 | "status-timestamps": { 185 | "planned-at": "2020-04-14T06:34:22+00:00", 186 | "planning-at": "2020-04-14T06:34:06+00:00", 187 | "plan-queued-at": "2020-04-14T06:34:06+00:00", 188 | "cost-estimated-at": "2020-04-14T06:34:29+00:00", 189 | "plan-queueable-at": "2020-04-14T06:34:06+00:00", 190 | "cost-estimating-at": "2020-04-14T06:34:22+00:00" 191 | }, 192 | "status": "cost_estimated", 193 | "trigger-reason": "manual", 194 | "permissions": { 195 | "can-apply": true, 196 | "can-cancel": true, 197 | "can-discard": true, 198 | "can-force-execute": true, 199 | "can-force-cancel": true, 200 | "can-override-policy-check": true 201 | } 202 | }, 203 | "relationships": { 204 | "workspace": { 205 | "data": { 206 | "id": "ws-ajLLjugn2ngooBV9", 207 | "type": "workspaces" 208 | } 209 | }, 210 | "apply": { 211 | "data": { 212 | "id": "apply-YnTxd3Jyca6QxRyz", 213 | "type": "applies" 214 | }, 215 | "links": { 216 | "related": "/api/v2/runs/run-bc4WDCExqr5CF4Ad/apply" 217 | } 218 | }, 219 | "configuration-version": { 220 | "data": { 221 | "id": "cv-Fcd8m1fT1SYHukBV", 222 | "type": "configuration-versions" 223 | }, 224 | "links": { 225 | "related": "/api/v2/runs/run-bc4WDCExqr5CF4Ad/configuration-version" 226 | } 227 | }, 228 | "cost-estimate": { 229 | "data": { 230 | "id": "ce-QNvdDKY5LaFfX2K6", 231 | "type": "cost-estimates" 232 | }, 233 | "links": { 234 | "related": "/api/v2/cost-estimates/ce-QNvdDKY5LaFfX2K6" 235 | } 236 | }, 237 | "created-by": { 238 | "data": { 239 | "id": "user-F1BcjnRCZtW8irfQ", 240 | "type": "users" 241 | }, 242 | "links": { 243 | "related": "/api/v2/runs/run-bc4WDCExqr5CF4Ad/created-by" 244 | } 245 | }, 246 | "plan": { 247 | "data": { 248 | "id": "plan-QDeb8Th3SKGjmMfP", 249 | "type": "plans" 250 | }, 251 | "links": { 252 | "related": "/api/v2/runs/run-bc4WDCExqr5CF4Ad/plan" 253 | } 254 | }, 255 | "run-events": { 256 | "data": [ 257 | { 258 | "id": "re-zEvX8pBeQmveXGCU", 259 | "type": "run-events" 260 | }, 261 | { 262 | "id": "re-H14w91kpaBkYSZE3", 263 | "type": "run-events" 264 | }, 265 | { 266 | "id": "re-PoqrfPvkTLeuRUyu", 267 | "type": "run-events" 268 | }, 269 | { 270 | "id": "re-DQHX1WNuvtmkUf8e", 271 | "type": "run-events" 272 | }, 273 | { 274 | "id": "re-G6XjkF2uCbG5LJ9T", 275 | "type": "run-events" 276 | }, 277 | { 278 | "id": "re-7LsT4e4tKqUxk8bn", 279 | "type": "run-events" 280 | } 281 | ], 282 | "links": { 283 | "related": "/api/v2/runs/run-bc4WDCExqr5CF4Ad/run-events" 284 | } 285 | }, 286 | "policy-checks": { 287 | "data": [], 288 | "links": { 289 | "related": "/api/v2/runs/run-bc4WDCExqr5CF4Ad/policy-checks" 290 | } 291 | }, 292 | "comments": { 293 | "data": [], 294 | "links": { 295 | "related": "/api/v2/runs/run-bc4WDCExqr5CF4Ad/comments" 296 | } 297 | } 298 | }, 299 | "links": { 300 | "self": "/api/v2/runs/run-bc4WDCExqr5CF4Ad" 301 | } 302 | } 303 | ], 304 | "links": { 305 | "self": "https://app.terraform.io/api/v2/workspaces/ws-ajLLjugn2ngooBV9/runs?page%5Bnumber%5D=1&page%5Bsize%5D=1", 306 | "first": "https://app.terraform.io/api/v2/workspaces/ws-ajLLjugn2ngooBV9/runs?page%5Bnumber%5D=1&page%5Bsize%5D=1", 307 | "prev": null, 308 | "next": "https://app.terraform.io/api/v2/workspaces/ws-ajLLjugn2ngooBV9/runs?page%5Bnumber%5D=2&page%5Bsize%5D=1", 309 | "last": "https://app.terraform.io/api/v2/workspaces/ws-ajLLjugn2ngooBV9/runs?page%5Bnumber%5D=2&page%5Bsize%5D=1" 310 | }, 311 | "meta": { 312 | "pagination": { 313 | "current-page": 1, 314 | "prev-page": null, 315 | "next-page": 2, 316 | "total-pages": 2, 317 | "total-count": 2 318 | } 319 | } 320 | } 321 | ``` 322 |
323 | 324 | 325 | このレスポンスに含まれる`.data[0].id`が Run ID になります。この例ですと、`run-bc4WDCExqr5CF4Ad`になります。 326 | 327 | それでは取得した Run ID に対して[Apply a Run の API](https://www.terraform.io/docs/cloud/api/run.html#apply-a-run)を実行します。 328 | 329 | ```shell 330 | export RUN_ID=<取得したRun ID> 331 | 332 | cat << EOF >> apply_run.json 333 | { 334 | "comment": "うん、いいねっ!" 335 | } 336 | EOF 337 | 338 | curl --header "Authorization: Bearer ${TOKEN}" --header "Content-Type: application/vnd.api+json" --request POST --data @apply_run.json https://app.terraform.io/api/v2/runs/${RUN_ID}/actions/apply 339 | ``` 340 | 341 | この API が成功すると`Confirm & Apply`が承認され Apply が実行されているはずです。 342 | 343 | ## まとめ 344 | 345 | Terraform cloud 及び Terraform enterprise は[ほぼ全ての機能が API でアクセス](https://www.terraform.io/docs/cloud/api/index.html)できます。ここでは既存の Workspace に対し Terraform コードをアップロードして Run を行いました。それ以外にも Workspace の Lock や Run の実行状態の取得など、非常に細かに制御する事ができます。 346 | 347 | - 皆様のワークフローに合わせた作り込み 348 | - Terraform バイナリが使えないような環境からも Provisioning 可能 349 | - CI/CD ツール 350 | - 独自の Provisioning システム 351 | - コンテナやサーバーレス 352 | - など 353 | 354 | ぜひ Terraform cloud API を活用してハッピーな Provisioning ライフをおくってください! 355 | -------------------------------------------------------------------------------- /contents/tf-api.md: -------------------------------------------------------------------------------- 1 | # Terraform Enterprise API を試す 2 | 3 | ここまで GUI で操作を行ってきましたが、TFE では API による操作も可能で CI パイプラインによる自動化の運用のワークフローなどに組み込むことが可能です。 4 | 5 | Terraform に関わる操作に加えてユーザ管理など様々な運用作業を API で実施できます。 6 | 7 | **ここまでで実行したプロビジョニングが全て Destroy されていることを確認してこの手順を進めて下さい。** 8 | 9 | ## トークンを取得する 10 | 11 | まず API を利用するにはトークンを取得する必要があります。 12 | 13 | [ユーザセッティング](https://app.terraform.io/app/settings/tokens)の画面からトークンを作成します。 14 | 15 | 16 | 17 | 18 | 19 | 上記のように任意のトークンの名前を入力して、`Generate Token`をクリックしてください。 20 | 21 | トークンが生成されるはずです。トークンはここでしか表示されないのでコピーして保存するかダウンロードをしてください。 22 | 23 | トークンを生成したらテストしてみます。 24 | 25 | ```shell 26 | $ export TF_TOKEN= 27 | 28 | $ curl \ 29 | --header "Authorization: Bearer $TF_TOKEN" \ 30 | --header "Content-Type: application/vnd.api+json" \ 31 | --request GET \ 32 | https://app.terraform.io/api/v2/account/details | jq 33 | ``` 34 | 35 | `account/details`を実行し、以下のようにユーザ情報が出力されるはずです。 36 | 37 |
出力例 38 | 39 | ```json 40 | { 41 | "data": { 42 | "id": "user-9szJo2sfxhVLevKp", 43 | "type": "users", 44 | "attributes": { 45 | "username": "tkaburagi", 46 | "is-service-account": false, 47 | "avatar-url": "https://www.gravatar.com/avatar/98f321325ba59b28d4af94339fb5e3e4?s=100&d=mm", 48 | "password": null, 49 | "v2-only": true, 50 | "enterprise-support": true, 51 | "is-site-admin": true, 52 | "is-sso-login": false, 53 | "two-factor": { 54 | "enabled": true, 55 | "verified": true 56 | }, 57 | "email": "kabu@hashicorp.com", 58 | "unconfirmed-email": null, 59 | "has-git-hub-app-token": false, 60 | "permissions": { 61 | "can-create-organizations": true, 62 | "can-change-email": true, 63 | "can-change-username": true 64 | } 65 | }, 66 | "relationships": { 67 | "authentication-tokens": { 68 | "links": { 69 | "related": "/api/v2/users/user-9szJo2sfxhVLevKp/authentication-tokens" 70 | } 71 | } 72 | }, 73 | "links": { 74 | "self": "/api/v2/users/user-9szJo2sfxhVLevKp" 75 | } 76 | } 77 | } 78 | ``` 79 |
80 | 81 | ## Terraform の操作を API で行う 82 | 83 | ここではワークスペース作成から実際のプロビジョニングを行ってみましょう。 84 | 85 | ### ワークスペース作成 86 | 87 | まずはワークスペースを作成してみます。 88 | 89 | Workspace の一覧はこちらです。 90 | 91 | ```shell 92 | $ export TF_ORG= 93 | 94 | $ curl \ 95 | --header "Authorization: Bearer $TF_TOKEN" \ 96 | --header "Content-Type: application/vnd.api+json" \ 97 | --request GET \ 98 | https://app.terraform.io/api/v2/organizations/${TF_ORG}/workspaces | jq -r ".data[].attributes.name" 99 | ``` 100 | 101 | 次にワークスペースを作成します。VCS の章で作成した設定から GitHub の`OAUTH_TOKEN_ID`を取得します。トップ画面の`Setting` -> `VCS Providers` から値をコピーして下さい。 102 | 103 | 104 | 105 | 106 | 107 | 108 | ```shell 109 | $ export GITHUB_USERNAME= 110 | $ export OAUTH_TOKEN_ID= 111 | 112 | $ cat << EOF > payload-workspace.json 113 | { 114 | "data": { 115 | "attributes": { 116 | "name": "new-workspace", 117 | "terraform_version": "0.12.6", 118 | "working-directory": "", 119 | "vcs-repo": { 120 | "identifier": "${GITHUB_USERNAME}/tf-handson-workshop", 121 | "oauth-token-id": "${OAUTH_TOKEN_ID}", 122 | "branch": "", 123 | "default-branch": true 124 | } 125 | }, 126 | "type": "workspaces" 127 | } 128 | } 129 | EOF 130 | 131 | $ curl \ 132 | --header "Authorization: Bearer $TF_TOKEN" \ 133 | --header "Content-Type: application/vnd.api+json" \ 134 | --request POST \ 135 | --data @payload.json \ 136 | https://app.terraform.io/api/v2/organizations/${TF_ORG}/workspaces | jq 137 | ``` 138 | 139 | `organizations/ORG_ID/workspaces`を実行し、一つワークスペースが作成されました。再度一覧を見てみましょう。一つワークスペースが作成されているはずです。ここで出力される`.data.id`の`ws-********`の値はワークスペースの ID です後で利用するのでメモしておいてください。 140 | 141 | ```shell 142 | $ export WS_ID= 143 | 144 | $ curl \ 145 | --header "Authorization: Bearer $TF_TOKEN" \ 146 | --header "Content-Type: application/vnd.api+json" \ 147 | --request GET \ 148 | https://app.terraform.io/api/v2/organizations/${TF_ORG}/workspaces | jq -r ".data[].attributes.name" 149 | ``` 150 | 151 | ```shell 152 | new-workspace 153 | handson-workshop 154 | ``` 155 | 156 | ワークスペースの情報を確認して VCS の連携の設定などが正しく反映されているか確認してください。`organizations/${TF_ORG}/workspaces/`がエンドポイントです。 157 | 158 | ```shell 159 | $ curl \ 160 | --header "Authorization: Bearer $TF_TOKEN" \ 161 | --header "Content-Type: application/vnd.api+json" \ 162 | --request GET \ 163 | https://app.terraform.io/api/v2/organizations/${TF_ORG}/workspaces/new-workspace | jq 164 | ``` 165 | 166 | ### 変数のセット 167 | 168 | 次に変数をセットします。変数のセットは JSON で定義した変数を`/vars`のエンドポイントに POST します。取得する時は同エンドポイントに GET します。 169 | 170 | 先ほど作ったワークスペースで試してみましょう。 171 | 172 | ```shell 173 | $ curl \ 174 | --header "Authorization: Bearer $TF_TOKEN" \ 175 | --header "Content-Type: application/vnd.api+json" \ 176 | --request GET \ 177 | https://app.terraform.io/api/v2/vars\?filter%5Borganization%5D%5Bname%5D\=${TF_ORG}\&filter%5Bworkspace%5D%5Bname%5D\=handson-workshop | jq 178 | ``` 179 | 180 | セットしたインスタンス数の値が見れる一方、`senstive`としてセットした AWS のキーなどは`null`と表示されるでしょう。 181 | 182 | それでは変数をセットしていきます。JSON で変数をセットするために必要なパラメータは[こちら](https://www.terraform.io/docs/cloud/api/variables.html)を参照してください。まずは`hello_tf_instance_count`です。 183 | 184 | ```shell 185 | $ cat << EOF > payload-var.json 186 | { 187 | "data": { 188 | "type": "vars", 189 | "attributes": { 190 | "key": "hello_tf_instance_count", 191 | "value": "1", 192 | "sensitive": false, 193 | "category": "terraform", 194 | "hcl": false, 195 | "description": null 196 | }, 197 | "relationships": { 198 | "configurable": { 199 | "data": { 200 | "id": "${WS_ID}", 201 | "type": "workspaces" 202 | } 203 | } 204 | } 205 | } 206 | } 207 | EOF 208 | 209 | $ curl \ 210 | --header "Authorization: Bearer $TF_TOKEN" \ 211 | --header "Content-Type: application/vnd.api+json" \ 212 | --request POST \ 213 | --data @payload-var.json \ 214 | https://app.terraform.io/api/v2/vars 215 | ``` 216 | 217 | 次に`hello_tf_instance_type`をセットします。 218 | 219 | ```shell 220 | $ cat << EOF > payload-var.json 221 | { 222 | "data": { 223 | "type": "vars", 224 | "attributes": { 225 | "key": "hello_tf_instance_type", 226 | "value": "t2.micro", 227 | "sensitive": false, 228 | "category": "terraform", 229 | "hcl": false, 230 | "description": null 231 | }, 232 | "relationships": { 233 | "configurable": { 234 | "data": { 235 | "id": "${WS_ID}", 236 | "type": "workspaces" 237 | } 238 | } 239 | } 240 | } 241 | } 242 | EOF 243 | 244 | $ curl \ 245 | --header "Authorization: Bearer $TF_TOKEN" \ 246 | --header "Content-Type: application/vnd.api+json" \ 247 | --request POST \ 248 | --data @payload-var.json \ 249 | https://app.terraform.io/api/v2/vars 250 | ``` 251 | 252 | セットした変数を確認してみましょう。 253 | 254 | ```shell 255 | $ curl \ 256 | --header "Authorization: Bearer $TF_TOKEN" \ 257 | --header "Content-Type: application/vnd.api+json" \ 258 | --request GET \ 259 | https://app.terraform.io/api/v2/vars\?filter%5Borganization%5D%5Bname%5D\=${TF_ORG}\&filter%5Bworkspace%5D%5Bname%5D\=new-workspace | jq 260 | ``` 261 | 262 | 残りの変数をセットしていきますが、現在 TFE API ではデータのリスト型をサポートしていないため一つ一つファイルを定義していく必要があります。 263 | 264 | ここでは API とは関係ありませんが、ここでは Terraform の`Terraform Enterprise Provider`を使ってバルクでセットしてみます。Terraform Enterprise Provider については[こちら](https://www.terraform.io/docs/providers/tfe/index.html)を参照して下さい。 265 | 266 | 残りの`aws_access_key`, `aws_secret_key`, `ami`, `region`, `confirm_destroy`をまとめてセットしていきます。 267 | 268 | ```shell 269 | $ mkdir tfe-provider 270 | 271 | $ cat << EOF > tfe-provider/main.tf 272 | terraform { 273 | required_version = "~> 0.12" 274 | } 275 | 276 | provider "tfe" { 277 | hostname = var.hostname 278 | token = var.token 279 | } 280 | 281 | resource "tfe_variable" "aws_access_key" { 282 | key = "access_key" 283 | value = var.aws_access_key 284 | category = "terraform" 285 | sensitive = true 286 | workspace_id = var.workspace_id 287 | } 288 | 289 | resource "tfe_variable" "aws_secret_key" { 290 | key = "secret_key" 291 | value = var.aws_secret_key 292 | category = "terraform" 293 | sensitive = true 294 | workspace_id = var.workspace_id 295 | } 296 | 297 | resource "tfe_variable" "ami" { 298 | key = "ami" 299 | value = "ami-06d9ad3f86032262d" 300 | category = "terraform" 301 | sensitive = false 302 | workspace_id = var.workspace_id 303 | } 304 | 305 | resource "tfe_variable" "region" { 306 | key = "region" 307 | value = "ap-northeast-1" 308 | category = "terraform" 309 | sensitive = false 310 | workspace_id = var.workspace_id 311 | } 312 | 313 | resource "tfe_variable" "confirm_destroy" { 314 | key = "CONFIRM_DESTROY" 315 | value = 1 316 | category = "env" 317 | sensitive = false 318 | workspace_id = var.workspace_id 319 | } 320 | EOF 321 | 322 | $ cat << EOF > tfe-provider/variables.tf 323 | variable "hostname" { 324 | default = "app.terraform.io" 325 | } 326 | variable "token" {} 327 | variable "workspace_id" { 328 | default = "${TF_ORG}/new-workspace" 329 | } 330 | variable "aws_access_key" {} 331 | variable "aws_secret_key" {} 332 | EOF 333 | ``` 334 | 335 | `terraform plan & apply`してみましょう。 336 | 337 | ```shell 338 | $ cd tfe-provider 339 | $ terraform init 340 | $ terraform plan 341 | $ terraform apply -auto-approve 342 | $ cd .. 343 | ``` 344 | 345 | `aws_access_key`, `aws_secret_key`, `token`の入力が必要です。`token`には Terraform Enterprise のトークンを入力します。 346 | 347 | Apply が終了したら変数を確認してください。 348 | 349 | ```shell 350 | $ curl \ 351 | --header "Authorization: Bearer $TF_TOKEN" \ 352 | --header "Content-Type: application/vnd.api+json" \ 353 | --request GET \ 354 | https://app.terraform.io/api/v2/vars\?filter%5Borganization%5D%5Bname%5D\=${TF_ORG}\&filter%5Bworkspace%5D%5Bname%5D\=new-workspace | jq 355 | ``` 356 | 357 | ### プロビジョニングを実行 358 | 359 | それでは最後にプロビジョニングと環境の削除をやってみます。 360 | 361 | まず`/runs`エンドポイントでプランを作成しましょう。プランにはプランに含めるメッセージやワークスペースの ID などが必要です。 362 | 363 | これを JSON で定義し、`runs`エンドポイントに POST することでプランが作成されます。 364 | 365 | ```shell 366 | $ cat << EOF > payload-plans.json 367 | { 368 | "data": { 369 | "attributes": { 370 | "is-destroy":false, 371 | "message": "This is an API Driven Run" 372 | }, 373 | "type":"runs", 374 | "relationships": { 375 | "workspace": { 376 | "data": { 377 | "type": "workspaces", 378 | "id": "${WS_ID}" 379 | } 380 | } 381 | } 382 | } 383 | } 384 | EOF 385 | 386 | $ curl \ 387 | --header "Authorization: Bearer $TF_TOKEN" \ 388 | --header "Content-Type: application/vnd.api+json" \ 389 | --request POST \ 390 | --data @payload-plans.json \ 391 | https://app.terraform.io/api/v2/runs | jq > plan-response-body.json 392 | ``` 393 | 394 | これで Plan が開始されているはずです。`plan-response-body.json`の内容を確認してみてください。 395 | 396 | 以下の`workspaces/${WS_ID}/runs`で状態を確認してみましょう。 397 | 398 | ```shell 399 | $ curl \ 400 | --header "Authorization: Bearer $TF_TOKEN" \ 401 | --header "Content-Type: application/vnd.api+json" \ 402 | --request GET \ 403 | https://app.terraform.io/api/v2/workspaces/${WS_ID}/runs | jq 404 | ``` 405 | 406 | 以下のように何度か実行すると`status`のパラメータが変化しているでしょう。 407 | 408 | ```json 409 | { 410 | "data": [ 411 | { 412 | "id": "run-3YUm7dKKh6qHQo1y", 413 | "type": "runs", 414 | "attributes": { 415 | "actions": { 416 | "is-cancelable": false, 417 | "is-confirmable": true, 418 | "is-discardable": true, 419 | "is-force-cancelable": false 420 | }, 421 | "canceled-at": null, 422 | "created-at": "2019-12-12T07:45:21.828Z", 423 | "has-changes": true, 424 | "is-destroy": false, 425 | "message": "This is an API Driven Run", 426 | "plan-only": false, 427 | "source": "tfe-api", 428 | "status-timestamps": { 429 | "planned-at": "2019-12-12T07:45:50+00:00", 430 | "planning-at": "2019-12-12T07:45:22+00:00", 431 | "plan-queued-at": "2019-12-12T07:45:22+00:00", 432 | "cost-estimated-at": "2019-12-12T07:45:55+00:00", 433 | "plan-queueable-at": "2019-12-12T07:45:21+00:00", 434 | "cost-estimating-at": "2019-12-12T07:45:50+00:00" 435 | }, 436 | "status": "cost_estimated", 437 | "trigger-reason": "manual", 438 | "permissions": { 439 | "can-apply": true, 440 | "can-cancel": true, 441 | "can-discard": true, 442 | "can-force-execute": true, 443 | "can-force-cancel": true, 444 | "can-override-policy-check": false 445 | } 446 | }, 447 | ``` 448 | 449 | 上記の`id`のパラメータをメモして環境変数にセットします。 450 | 451 | ```shell 452 | $ export RUN_ID=run-3YUm7dKKh6qHQo1y 453 | ``` 454 | 455 | Plan が終わったらプラン内容を確認します。先ほど取得した`plan-response-body.json`から Plan ID を取得しましょう。 456 | 457 | ```shell 458 | $ export PLAN_ID=$(cat plan-response-body.json | jq -r '.data.relationships.plan.data.id') 459 | 460 | $ curl \ 461 | --header "Authorization: Bearer $TF_TOKEN" \ 462 | --header "Content-Type: application/vnd.api+json" \ 463 | --request GET \ 464 | https://app.terraform.io/api/v2/plans/${PLAN_ID} | jq > plan-details.json 465 | ``` 466 | 467 | `plan-details.json`の内容を確認してください。ここから`log-read-url`のファイルを取得し、プランの変更内容を確認します。 468 | 469 | ```shell 470 | wget $(cat plan-details.json | jq -r '.data.attributes."log-read-url"' 471 | ``` 472 | 473 | テキストファイルが出力されますので内容を確認してください。 474 | 475 | 最後に Apply をします。プランの結果、`apply`や`dsicard`を選択できます。 476 | 477 | Apply の場合は、`/runs/:run_id/actions/apply`がエンドポイントです。 478 | 479 | ```shell 480 | $ cat << EOF > payload-comment.json 481 | { 482 | "comment":"Looks good to me" 483 | } 484 | EOF 485 | ``` 486 | 487 | コメントを JSON で定義して、Apply をしてみましょう。 488 | 489 | ```shell 490 | $ curl \ 491 | --header "Authorization: Bearer $TF_TOKEN" \ 492 | --header "Content-Type: application/vnd.api+json" \ 493 | --request POST \ 494 | --data @payload-comment.json \ 495 | https://app.terraform.io/api/v2/runs/${RUN_ID}/actions/apply 496 | ``` 497 | 498 | Run の状態を何度か見ると`applied`となり、プロビジョニングが完了します。 499 | 500 | ```shell 501 | $ curl \ 502 | --header "Authorization: Bearer $TF_TOKEN" \ 503 | --header "Content-Type: application/vnd.api+json" \ 504 | --request GET \ 505 | https://app.terraform.io/api/v2/runs/${RUN_ID} 506 | ``` 507 | 508 | ### 環境をクリーンナップする 509 | 510 | 最後に`destroy`を実行し、環境をクリーンナップしましょう。Destroy は Plan と同一のエンドポイントに`data.attributes.is-destroy`が`true`にセットされた JSON を実行させます。 511 | 512 | ```shell 513 | $ cat << EOF > payload-destroy.json 514 | { 515 | "data": { 516 | "attributes": { 517 | "is-destroy":true, 518 | "message": "Will be destroyed" 519 | }, 520 | "type":"runs", 521 | "relationships": { 522 | "workspace": { 523 | "data": { 524 | "type": "workspaces", 525 | "id": "${WS_ID}" 526 | } 527 | } 528 | } 529 | } 530 | } 531 | EOF 532 | ``` 533 | 534 | `is-destroy`が True になっていることを確認してください。これをプランしてみましょう。 535 | 536 | ```shell 537 | $ curl \ 538 | --header "Authorization: Bearer $TF_TOKEN" \ 539 | --header "Content-Type: application/vnd.api+json" \ 540 | --request POST \ 541 | --data @payload-destroy.json \ 542 | https://app.terraform.io/api/v2/runs | jq 543 | ``` 544 | 545 | 次に、コメントをつけて Destroy を Apply します。上記で出力された`Run ID`は環境変数にセットしておきます。 546 | 547 | ```shell 548 | $ export RUN_ID=run-msjedz6ExGfGZuqm 549 | ``` 550 | 551 | `actions/apply`のエンドポイントにコメントの JSON を POST します。 552 | 553 | ```shell 554 | $ cat << EOF > payload-comment.json 555 | { 556 | "comment":"Yes, Let's Destroy" 557 | } 558 | EOF 559 | 560 | $ curl \ 561 | --header "Authorization: Bearer $TF_TOKEN" \ 562 | --header "Content-Type: application/vnd.api+json" \ 563 | --request POST \ 564 | --data @payload-comment.json \ 565 | https://app.terraform.io/api/v2/runs/${RUN_ID}/actions/apply 566 | ``` 567 | 568 | 最後に Run の内容を確認し、Destroy が終了していることを確認してください。 569 | 570 | ```shell 571 | $ curl \ 572 | --header "Authorization: Bearer $TF_TOKEN" \ 573 | --header "Content-Type: application/vnd.api+json" \ 574 | --request GET \ 575 | https://app.terraform.io/api/v2/runs/${RUN_ID} 576 | ``` 577 | 578 | 以上で API の利用の章は終了です。TFE API ではワークスペースやプロビジョニングに関する操作の他に、ユーザ管理系のオプレーションなど様々な作業を実施することができ、自動化のプロセスに組み込むことが可能です。 579 | 580 | ## 参考リンク 581 | * [Terraform Cloud API Document](https://www.terraform.io/docs/cloud/api/index.html) 582 | * [API Drive Run](https://www.terraform.io/docs/cloud/run/api.html) -------------------------------------------------------------------------------- /contents/sentinel.md: -------------------------------------------------------------------------------- 1 | # Sentinel による Policy as Code 2 | 3 | Terraform は便利なツールですが、多くのユーザが利用し大規模な運用になるとガバンナンスをきかせユーザの行動を制御することが運用上課題となります。有償版では HashiCorp が開発する[Sentinel](https://www.hashicorp.com/sentinel)というフレームワークを利用し、ポリシーを定義することができます。 4 | 5 | 定義できるポリシーは多岐に渡りますが、下記のような一例としてユースケースがあります。 6 | 7 | * Terrafrom 実行時間の制御 8 | * AZ/Region の制約 9 | * タグの確認 10 | * CIDR やネットワーク設定の確認 11 | * 特定リソースの利用禁止 12 | 13 | ## Sentinel コードの作成 14 | それではまずは Sentinel を利用するための設定を行います。Sentinel は最低限二つのファイルが必要です。一つは`sentinel.hcl`、もう一つは`.sentinel`です。 15 | 16 | ``` 17 | GitHub上に`sentinel-handson-workshop`という名前のパブリックレポジトリを作成してください。 18 | ``` 19 | 20 | ```shell 21 | $ cd path/to/tf-workspace 22 | $ mkdir sentinel-handson-workshop 23 | $ cd sentinel-handson-workshop 24 | ``` 25 | 26 | 以下の二つのファイルを追加します。 27 | 28 | * 全クラウド共通のファイル (sentinel.hcl) 29 | 30 | ```shell 31 | $ cat < sentinel.hcl 32 | policy "first-policy" { 33 | enforcement_level = "hard-mandatory" 34 | } 35 | EOF 36 | ``` 37 | 38 | * クラウドごとに変化するファイル (first-policy.sentinel) 39 | 40 | ``` 41 | WIP 42 | ``` 43 | 44 |
GCPの場合はこちら 45 | 46 | ``` 47 | import "tfplan/v2" as tfplan 48 | 49 | mandatory_labels = [ 50 | "ttl", 51 | ] 52 | 53 | instances = filter tfplan.resource_changes as _, rc { 54 | rc.mode is "managed" and 55 | rc.type is "google_compute_instance" 56 | } 57 | 58 | tags = instances["google_compute_instance.vm_instance[0]"]["change"]["after"]["labels"] 59 | 60 | main = rule { 61 | all mandatory_labels as t { 62 | tags contains t 63 | } 64 | } 65 | ``` 66 |
67 | 68 |
Azureの場合はこちら 69 | 70 | ``` 71 | import "tfplan/v2" as tfplan 72 | 73 | mandatory_tags = [ 74 | "ttl", 75 | ] 76 | 77 | instances = filter tfplan.resource_changes as _, rc { 78 | rc.mode is "managed" and 79 | rc.type is "azurerm_virtual_machine" 80 | } 81 | 82 | tags = instances["azurerm_virtual_machine.main[0]"]["change"]["after"]["tags"] 83 | 84 | main = rule { 85 | all mandatory_tags as t { 86 | tags contains t 87 | } 88 | } 89 | ``` 90 |
91 | 92 | 93 | このようになっていれば OK です。 94 | 95 | ``` 96 | . 97 | ├── sentinel-handson-workshop 98 | │   ├── first-policy.sentinel 99 | │   └── sentinel.hcl 100 | └── tf-handson-workshop 101 | ├── main.tf 102 | └── variables.tf 103 | ``` 104 | 105 | `sentinel.hcl`のファイルは実際のポリシーが定義されているコードの設定を行います。`enforcement_level`を定義し、そのポリシーの強制度合いを設定します。 106 | 107 | * soft-mandatory 108 | * ポリシーに引っかかりエラーになった時にそれをオーバーライドして実行を許可するか、拒否するかを選択できるモード 109 | * hard-mandatory 110 | * ポリシーに引っかかったら必ず実行を拒否するモード 111 | * advisory 112 | * 実行は許可するが、警告を出すモード 113 | 114 | `first-policy.sentinel`のファイルは実際のポリシーコードです。ここの例は全てのインスタンスに対してタグがついているかを確認しています。 115 | 116 | それではこれを GitHub にプッシュしてみます。 117 | 118 | ```shell 119 | $ echo "# sentinel-handson-workshop" >> README.md 120 | $ export GITURL= 121 | $ git init 122 | $ git add . 123 | $ git commit -m "first commit" 124 | $ git branch -M main 125 | $ git remote add origin ${GITURL} 126 | $ git push -u origin main 127 | ``` 128 | 129 | ## TFC の設定 130 | 次に TFC 側の設定です。トップ画面の一番上のタブから`Settings`を選択し、 131 | 132 | 133 | 134 | 135 | 136 | `Policy Sets`を選び`Connect a new Policy Set`をクリックします。 137 | 138 | 139 | 140 | 141 | 142 | `Connect a Policy Set`で先程作った`sentinel-handson-workshop`のレポジトリを選択します。 143 | 144 | 145 | 146 | 147 | 148 | 次の画面で以下のように入力してください。名前などは任意で構いません。**workspace を選んだら ADD WORKSPACE を押すのを忘れないでください。** 149 | 150 | 151 | 152 | 153 | 154 | ## ポリシーを試してみる 155 | 156 | それでは実行してみましょう。ワークスペースの`Actions` -> `Start a New Plan` を選択し、Run をキックします。 157 | 158 | 159 | 160 | 161 | 162 | 前回と違い Policy Check のワークフローが追加されていることがわかります。 163 | 164 | 165 | 166 | 167 | 168 | 程なくするとポリシーチェックが開始されエラーになるでしょう。 169 | 170 | 次にコードを修正し、再度コミットしてみます。`path/to/tf-handson-workshop/main.tf`のコードを以下のように修正してください。 171 | 172 | ```hcl 173 | terraform { 174 | } 175 | 176 | provider "aws" { 177 | access_key = var.access_key 178 | secret_key = var.secret_key 179 | region = var.region 180 | } 181 | 182 | resource "aws_instance" "hello-tf-instance" { 183 | ami = var.ami 184 | count = var.hello_tf_instance_count 185 | instance_type = var.hello_tf_instance_type 186 | tags = { 187 | owner = "me" 188 | ttl = "100" 189 | } 190 | } 191 | ``` 192 | 193 |
GCPの場合はこちら 194 | 195 | ``` 196 | terraform { 197 | } 198 | 199 | provider "google" { 200 | credentials = var.gcp_key 201 | project = var.project 202 | region = var.region 203 | } 204 | 205 | resource "google_compute_instance" "vm_instance" { 206 | name = "terraform-instance-${count.index}" 207 | machine_type = var.machine_type 208 | count = var.hello_tf_instance_count 209 | zone = "asia-northeast1-a" 210 | labels = { 211 | owner = "me", 212 | ttl = "100" 213 | } 214 | boot_disk { 215 | initialize_params { 216 | image = var.image 217 | } 218 | } 219 | 220 | network_interface { 221 | # A default network is created for all GCP projects 222 | network = "default" 223 | access_config { 224 | } 225 | } 226 | } 227 | ``` 228 |
229 | 230 |
Azureの場合はこちら 231 | 232 | ```hcl 233 | terraform { 234 | } 235 | 236 | provider "azurerm" { 237 | client_id = var.client_id 238 | tenant_id = var.tenant_id 239 | subscription_id = var.subscription_id 240 | client_secret = var.client_secret 241 | features {} 242 | } 243 | 244 | resource "azurerm_virtual_machine" "main" { 245 | name = "my-vm-${count.index}" 246 | count = var.hello_tf_instance_count 247 | location = var.location 248 | resource_group_name = azurerm_resource_group.example.name 249 | network_interface_ids = [azurerm_network_interface.example.*.id[count.index]] 250 | vm_size = var.vm_size 251 | 252 | os_profile { 253 | computer_name = "hostname" 254 | admin_username = "vmadmin" 255 | admin_password = var.admin_password 256 | } 257 | os_profile_linux_config { 258 | disable_password_authentication = false 259 | } 260 | tags = { 261 | owner = "me", 262 | ttl = "100" 263 | } 264 | storage_image_reference { 265 | publisher = "Canonical" 266 | offer = "UbuntuServer" 267 | sku = "16.04-LTS" 268 | version = "latest" 269 | } 270 | storage_os_disk { 271 | name = "my-osdisk-${count.index}" 272 | caching = "ReadWrite" 273 | create_option = "FromImage" 274 | managed_disk_type = "Standard_LRS" 275 | } 276 | } 277 | 278 | resource "azurerm_resource_group" "example" { 279 | name = "my-group" 280 | location = var.location 281 | } 282 | 283 | 284 | resource "azurerm_virtual_network" "example" { 285 | name = "my-network" 286 | address_space = ["10.0.0.0/16"] 287 | location = var.location 288 | resource_group_name = azurerm_resource_group.example.name 289 | } 290 | 291 | resource "azurerm_subnet" "example" { 292 | name = "my-subnet" 293 | resource_group_name = azurerm_resource_group.example.name 294 | virtual_network_name = azurerm_virtual_network.example.name 295 | address_prefix = "10.0.2.0/24" 296 | } 297 | 298 | resource "azurerm_network_interface" "example" { 299 | name = "my-nw-interface-${count.index}" 300 | count = var.hello_tf_instance_count 301 | location = var.location 302 | resource_group_name = azurerm_resource_group.example.name 303 | 304 | ip_configuration { 305 | name = "my-ip-config" 306 | subnet_id = azurerm_subnet.example.id 307 | private_ip_address_allocation = "Dynamic" 308 | } 309 | 310 | tags = { 311 | environment = "payground" 312 | } 313 | } 314 | ``` 315 |
316 | 317 | ```shell 318 | $ git add main.tf 319 | $ git commit -m "added tags" 320 | $ git push 321 | ``` 322 | 323 | 再度ワークスペースの Runs の中から最新の実行を選んでください。次はポリシーチェックをクリアし、Apply できるはずです。`confirm & apply`をクリックして Apply してみましょう。 324 | 325 | Apply が成功したら念の為インタンスにタグが付与されていることも確認しておきましょう。(GCP/Azure の場合は Web ブラウザから確認してください。) 326 | 327 | ```console 328 | $ aws ec2 describe-instances --query "Reservations[].Instances[].{InstanceId:InstanceId,State:State,Tags:Tags}" 329 | [ 330 | { 331 | "InstanceId": "i-0561854e3727d3704", 332 | "State": { 333 | "Code": 16, 334 | "Name": "running" 335 | }, 336 | "Tags": [ 337 | { 338 | "Value": "me", 339 | "Key": "owner" 340 | }, 341 | { 342 | "Value": "100", 343 | "Key": "ttl" 344 | } 345 | ] 346 | } 347 | ] 348 | ``` 349 | 350 | これで最初の Sentinel は終了です。ちなみにタグは有無だけではなく特定のタグがない場合を弾くなど、さらに細かく設定することもできます。 351 | 352 | ## Sentinel Simulator 353 | 354 | Sentinel で都度実際に実行して確認するのではなく`Sentinel Sumilator`を利用してテストを実施するのが普通です。テストには sentinel cli が必要なので[こちらから](https://docs.hashicorp.com/sentinel/downloads)ダウンロードしてください。 355 | 356 | `Apply`と`Test`の二つの機能があります。 357 | 358 | ### Apply 359 | 360 | apply コマンドは Sentinel のコードをローカルで実行して試すコマンドです。ポリシーが意図通り動いていることや Syntax や文法にエラーがないかなどを事前に確認することができます。 361 | 362 | Apply にはコンフィグレーションファイルというファイルが必要です。このファイルにはモックデータやテストケースを記述します。 363 | 364 | サンプルを一つ作ってみます。 365 | 366 | ```shell 367 | $ cd path/to/tf-workspace 368 | $ mkdir simulator-sample 369 | $ cd simulator-sample 370 | ``` 371 | 372 | ```shell 373 | $ cat < sentinel.json 374 | { 375 | "mock": { 376 | "time": { 377 | "hour": 9, 378 | "minute": 42 379 | } 380 | } 381 | } 382 | EOF 383 | ``` 384 | 385 | これがコンフィグレーションファイルとなります。この json は[time](https://docs.hashicorp.com/sentinel/imports/time)という Sentinel の標準で使える機能をモックし、仮のデータ`9時42分`として入れています。 386 | 387 | 次に Sentinel のコードを作ります。 388 | 389 | ```shell 390 | $ cat < foo.sentinel 391 | import "time" 392 | 393 | main = time.hour == 10 394 | EOF 395 | ``` 396 | 397 | ここでは 10 時かどうかを確認しているため Apply するとエラーになるはずです。Apply して試してみましょう。 398 | 399 | ```console 400 | $ sentinel apply foo.sentinel 401 | Fail 402 | 403 | Execution trace. The information below will show the values of all 404 | the rules evaluated and their intermediate boolean expressions. Note that 405 | some boolean expressions may be missing if short-circuit logic was taken. 406 | ``` 407 | 408 | コードを直して再度試してみます。`main = time.hour == 10` を`main = time.hour == 9`に変更して再度 Apply を実行します。 409 | 410 | ```console 411 | $ sentinel apply foo.sentinel 412 | Pass 413 | ``` 414 | 415 | データのモックには上記のように静的に指定する方法と Sentinel のコードで指定する方法があります。関数など JSON で静的にデータをモック出来ないタイプのデータがいくつかあります。ここでは関数を指定する方法を試してみます。 416 | 417 | まずは関数を一つ作ってみます。ここでの Sentinel はポリシーの定義ではなくあくまでも関数でのモックデータの定義なので`main`は必要ありません。 418 | 419 | ```shell 420 | $ cat < mock-foo.sentinel 421 | bar = func() { 422 | return "baz" 423 | } 424 | EOF 425 | ``` 426 | 427 | 次に新しいコンフィグを作ってモックデータに先ほど Sentinel で作った関数を指定します。 428 | 429 | ```shell 430 | $ cat < sentinel-2.json 431 | { 432 | "mock": { 433 | "foo": "mock-foo.sentinel" 434 | } 435 | } 436 | EOF 437 | ``` 438 | 439 | 最後に新しいポリシーの定義ファイルを作ります。 440 | 441 | ```shell 442 | $ cat < foo-2.sentinel 443 | import "foo" 444 | main = foo.bar() == "baz" 445 | EOF 446 | ``` 447 | 448 | モックデータで定義されている`foo`を import して、関数`bar`を実行し、実行結果が`baz`であるかどうかを判定しています。 449 | 450 | ```console 451 | $ sentinel apply -config=sentinel-2.json foo-2.sentinel 452 | Pass 453 | ``` 454 | 455 | Apply すると Pass となるはずです。 456 | 457 | ### Terraform のデータを利用する 458 | 459 | 上記の例ではシンプルなデータを使って Simulator の機能を試してきました。実際の Terraform の環境ではモックのデータを自分で作るにはかなりの手間がかかります。TFC では Setinel 用にワークスペースの最新の構成をモックデータとして Export する機能が入っています。 460 | 461 | 462 | 463 | 464 | 465 | Workspaces の Runs から最新の実行結果の`Plan finished`をクリックすると`Downloads Sentinel mocks`というボタンがあるのでこれをクリックしてモックデータをダウンロードし新しいフォルダを作ります。 466 | 467 | ```shell 468 | $ cd path/to/tf-workspace 469 | $ mkdir simulator-tf-sample simulator-tf-sample/testdata 470 | $ touch simulator-tf-sample/sentinel.hcl simulator-tf-sample/tags-check.sentinel 471 | $ tar xvfz path/to/run-gvXm387VP1VShKC1-sentinel-mocks.tar.gz -C simulator-tf-sample/testdata 472 | ``` 473 | 474 | 以下のような構造になれば OK です。 475 | 476 | ```console 477 | simulator-tf-sample 478 | ├── sentinel.hcl 479 | ├── tags-check.sentinel 480 | └── testdata 481 | ├── mock-tfconfig-v2.sentinel 482 | ├── mock-tfconfig.sentinel 483 | ├── mock-tfplan-v2.sentinel 484 | ├── mock-tfplan.sentinel 485 | ├── mock-tfrun.sentinel 486 | ├── mock-tfstate-v2.sentinel 487 | ├── mock-tfstate.sentinel 488 | └── sentinel.hcl 489 | ``` 490 | 491 | `testdata/`以下にコピーした 3 つのファイルには Sentinel で定義されたモックデータが入っています。全てを理解する必要はないので、これが最新の Terraform の状況をシミュレートしているとだけ押さえておけばとりあえず大丈夫です。 492 | 493 | sentinel.hcl を以下のように記述してください。 494 | 495 | ```hcl 496 | mock "tfconfig" { 497 | module { 498 | source = "testdata/mock-tfconfig.sentinel" 499 | } 500 | } 501 | 502 | mock "tfconfig/v1" { 503 | module { 504 | source = "testdata/mock-tfconfig.sentinel" 505 | } 506 | } 507 | 508 | mock "tfconfig/v2" { 509 | module { 510 | source = "testdata/mock-tfconfig-v2.sentinel" 511 | } 512 | } 513 | 514 | mock "tfplan" { 515 | module { 516 | source = "testdata/mock-tfplan.sentinel" 517 | } 518 | } 519 | 520 | mock "tfplan/v1" { 521 | module { 522 | source = "testdata/mock-tfplan.sentinel" 523 | } 524 | } 525 | 526 | mock "tfplan/v2" { 527 | module { 528 | source = "testdata/mock-tfplan-v2.sentinel" 529 | } 530 | } 531 | 532 | mock "tfstate" { 533 | module { 534 | source = "testdata/mock-tfstate.sentinel" 535 | } 536 | } 537 | 538 | mock "tfstate/v1" { 539 | module { 540 | source = "testdata/mock-tfstate.sentinel" 541 | } 542 | } 543 | ``` 544 | 545 | ダウンロードした環境をシミュレートする Sentinel ファイルをモックデータとして指定しています。実際のポリシーコードではこの`tfconfig`, `tfplan`,`tfstate`を import してポリシーを定義しローカルで実行します。 546 | 547 | 一番最初に試したタグの有無をチェックするポリシーを使って試してみたいと思います。`tags-check.sentinel`を以下のように編集します。 548 | 549 | ```sentinel 550 | WIP 551 | ``` 552 | 553 |
GCPの場合はこちら 554 | 555 | ```sentinel 556 | import "tfplan/v2" as tfplan 557 | 558 | mandatory_labels = [ 559 | "ttl", 560 | ] 561 | 562 | instances = filter tfplan.resource_changes as _, rc { 563 | rc.mode is "managed" and 564 | rc.type is "google_compute_instance" 565 | } 566 | 567 | tags = instances["google_compute_instance.vm_instance[0]"]["change"]["after"]["labels"] 568 | 569 | main = rule { 570 | all mandatory_labels as t { 571 | tags contains t 572 | } 573 | } 574 | ``` 575 |
576 | 577 |
Azureの場合はこちら 578 | 579 | ```sentinel 580 | import "tfplan/v2" as tfplan 581 | 582 | mandatory_tags = [ 583 | "ttl", 584 | ] 585 | 586 | instances = filter tfplan.resource_changes as _, rc { 587 | rc.mode is "managed" and 588 | rc.type is "azurerm_virtual_machine" 589 | } 590 | 591 | tags = instances["azurerm_virtual_machine.main[0]"]["change"]["after"]["tags"] 592 | 593 | main = rule { 594 | all mandatory_tags as t { 595 | tags contains t 596 | } 597 | } 598 | ``` 599 |
600 | 601 | 602 | `testdata/mock-tfplan.sentinel`を確認してみましょう。 603 | 604 | ```console 605 | $ grep -A 4 -n labels testdata/mock-tfplan-v2.sentinel 606 | "tags": { 607 | 24- "owner": "me", 608 | 25- "ttl": "100", 609 | 26- }, 610 | 27- "timeouts": null, 611 | -- 612 | 208: "tags.%": { 613 | 209- "computed": false, 614 | 210- "new": "2", 615 | 211- "old": "", 616 | 212- }, 617 | -- 618 | 213: "tags.owner": { 619 | 214- "computed": false, 620 | 215- "new": "me", 621 | 216- "old": "", 622 | 217- }, 623 | -- 624 | 218: "tags.ttl": { 625 | 219- "computed": false, 626 | 220- "new": "100", 627 | 221- "old": "", 628 | 222- }, 629 | -- 630 | 243: "volume_tags.%": { 631 | 244- "computed": true, 632 | 245- "new": "", 633 | 246- "old": "", 634 | 247- }, 635 | ``` 636 | 637 | タグがついたデータが入っておりテストが通るはずです。 638 | 639 | ```console 640 | $ sentinel apply tags-check.sentinel 641 | Pass 642 | ``` 643 | 644 | 最後にポリシーコードを変更して意図通り Fail するかを確認します。 645 | 646 | ```sentinel 647 | WIP 648 | ``` 649 | 650 |
GCPの場合はこちら 651 | 652 | ```sentinel 653 | import "tfplan/v2" as tfplan 654 | 655 | mandatory_labels = [ 656 | "ttl", 657 | "test", 658 | ] 659 | 660 | instances = filter tfplan.resource_changes as _, rc { 661 | rc.mode is "managed" and 662 | rc.type is "google_compute_instance" 663 | } 664 | 665 | tags = instances["google_compute_instance.vm_instance[0]"]["change"]["after"]["labels"] 666 | 667 | main = rule { 668 | all mandatory_labels as t { 669 | tags contains t 670 | } 671 | } 672 | ``` 673 |
674 | 675 |
Azureの場合はこちら 676 | 677 | ```sentinel 678 | WIP 679 | ``` 680 |
681 | 682 | 実際のモックデータには入っていない`test`タグを必須タグとしてポリシーをセットしました。 683 | 684 | ```console 685 | $ sentinel apply tags-check.sentinel 686 | Execution trace. The information below will show the values of all 687 | the rules evaluated. Note that some rules may be missing if 688 | short-circuit logic was taken. 689 | 690 | Note that for collection types and long strings, output may be 691 | truncated; re-run "sentinel apply" with the -json flag to see the 692 | full contents of these values. 693 | 694 | The trace is displayed due to a failed policy. 695 | 696 | Fail - tags.sentinel 697 | 698 | tags.sentinel:15:1 - Rule "main" 699 | Value: 700 | false 701 | ``` 702 | 703 | 意図通り`false`が返ってくるはずです。 704 | 705 | 706 | ## 参考リンク 707 | * [Sentinel](https://www.hashicorp.com/sentinel) 708 | * [Sentinel Docs](https://docs.hashicorp.com/sentinel/) 709 | * [Sentinel Terraform](https://www.terraform.io/docs/cloud/sentinel/index.html) 710 | * [Sentinel Language](https://docs.hashicorp.com/sentinel/language/) 711 | * [Mocking Terraform Data](https://www.terraform.io/docs/cloud/sentinel/mock.html) 712 | * [Sample Policies](https://github.com/hashicorp/terraform-guides/tree/master/governance) 713 | * [Sample Policies](https://www.terraform.io/docs/cloud/sentinel/examples.html) 714 | --------------------------------------------------------------------------------