├── code ├── 03.modules │ ├── random-file │ │ ├── outputs.tf │ │ ├── variables.tf │ │ └── main.tf │ ├── outputs.tf │ ├── version.tf │ └── main.tf ├── public-cloud │ ├── gcp-state │ │ ├── backend.tf │ │ └── main.tf │ ├── azure-vm │ │ ├── version.tf │ │ └── main.tf │ ├── gcp-pubsub │ │ ├── version.tf │ │ └── main.tf │ ├── azure-postgresql │ │ ├── variables.tf │ │ ├── pg-fs-db.tf │ │ ├── version.tf │ │ ├── outputs.tf │ │ └── main.tf │ ├── azure-aks │ │ ├── terraform │ │ │ ├── version.tf │ │ │ ├── outputs.tf │ │ │ ├── variables.tf │ │ │ └── main.tf │ │ └── kubernetes │ │ │ └── azure-vote.yaml │ └── azure-state │ │ └── main.tf ├── 02.providers │ └── version.tf ├── 01.introduction │ └── main.tf └── 04.states │ └── main.tf ├── pictures ├── 00.preface │ ├── .DS_Store │ ├── terraform-associate.certificate.png │ └── terraform-associate.certificate-larry.png ├── 01.introduction │ ├── logo.aws-cf.png │ ├── logo.chef.png │ ├── logo.puppet.jpg │ ├── logo.ansible.png │ ├── logo.terraform.png │ ├── arch.how-it-works.png │ ├── logo.vagrant-logo.png │ ├── introduction.download.png │ ├── logo.azure-resource-manager.jpg │ └── logo.google-deployment-manager.jpg ├── 04.states │ └── state.postgresql-table.png ├── public-cloud │ ├── azure │ │ ├── tenant-info.png │ │ ├── aks-cli.front-page.png │ │ ├── create-vm.add-vm.png │ │ ├── create-vm.config.png │ │ ├── create-vm.vm-list.png │ │ ├── subscription-list.png │ │ ├── create-azure-account.png │ │ ├── state.azure-storage.png │ │ ├── subscription-access.png │ │ ├── subscription-add-sp.png │ │ ├── subscription-add-role.png │ │ ├── postgresql.cli.connected.png │ │ ├── create-azure-account.portal.png │ │ ├── create-azure-account.profile.png │ │ ├── create-vm.download-ssh-key.png │ │ ├── postgresql.terraform.portal.png │ │ ├── terraform-create-resources.png │ │ ├── create-azure-account.visa-card.png │ │ ├── create-azure-account.with-github.png │ │ ├── create-service-principal.app-reg.png │ │ ├── create-service-principal.app-reg2.png │ │ ├── create-service-principal.config-password.png │ │ ├── create-service-principal.markdown-password.png │ │ └── create-service-principal.add-client-password.png │ └── gcp │ │ ├── init-gcp-sdk.pubsub.png │ │ ├── init-gcp-sdk.new-project.png │ │ ├── init-gcp-sdk.new-sa-key.png │ │ ├── init-gcp-sdk.new-sa-name.png │ │ ├── init-gcp-sdk.new-sa-role.png │ │ ├── terraform-gcp-pubsub.pull.png │ │ ├── terraform-gcs.bucket-state.png │ │ ├── init-gcp-sdk.new-service-account.png │ │ └── terraform-gcp-pubsub.console-pub.png └── 02.providers │ └── providers.official-site.png ├── .gitignore ├── Terraform常用命令.md ├── README.md ├── 03.Modules模块化.md ├── 02.Providers插件管理.md ├── Terraform在公有云GCP上的应用.md ├── 04.States状态管理.md ├── 05.HCL语法.md ├── LICENSE ├── 01.Terraform初相识.md ├── Functions函数.md └── Terraform在公有云Azure上的应用.md /code/03.modules/random-file/outputs.tf: -------------------------------------------------------------------------------- 1 | output "file_name" { 2 | value = local_file.file.filename 3 | } -------------------------------------------------------------------------------- /pictures/00.preface/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/00.preface/.DS_Store -------------------------------------------------------------------------------- /pictures/01.introduction/logo.aws-cf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/01.introduction/logo.aws-cf.png -------------------------------------------------------------------------------- /pictures/01.introduction/logo.chef.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/01.introduction/logo.chef.png -------------------------------------------------------------------------------- /pictures/01.introduction/logo.puppet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/01.introduction/logo.puppet.jpg -------------------------------------------------------------------------------- /pictures/01.introduction/logo.ansible.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/01.introduction/logo.ansible.png -------------------------------------------------------------------------------- /pictures/01.introduction/logo.terraform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/01.introduction/logo.terraform.png -------------------------------------------------------------------------------- /pictures/04.states/state.postgresql-table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/04.states/state.postgresql-table.png -------------------------------------------------------------------------------- /pictures/public-cloud/azure/tenant-info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/azure/tenant-info.png -------------------------------------------------------------------------------- /pictures/01.introduction/arch.how-it-works.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/01.introduction/arch.how-it-works.png -------------------------------------------------------------------------------- /pictures/01.introduction/logo.vagrant-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/01.introduction/logo.vagrant-logo.png -------------------------------------------------------------------------------- /pictures/01.introduction/introduction.download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/01.introduction/introduction.download.png -------------------------------------------------------------------------------- /pictures/02.providers/providers.official-site.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/02.providers/providers.official-site.png -------------------------------------------------------------------------------- /pictures/public-cloud/azure/aks-cli.front-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/azure/aks-cli.front-page.png -------------------------------------------------------------------------------- /pictures/public-cloud/azure/create-vm.add-vm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/azure/create-vm.add-vm.png -------------------------------------------------------------------------------- /pictures/public-cloud/azure/create-vm.config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/azure/create-vm.config.png -------------------------------------------------------------------------------- /pictures/public-cloud/azure/create-vm.vm-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/azure/create-vm.vm-list.png -------------------------------------------------------------------------------- /pictures/public-cloud/azure/subscription-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/azure/subscription-list.png -------------------------------------------------------------------------------- /pictures/public-cloud/gcp/init-gcp-sdk.pubsub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/gcp/init-gcp-sdk.pubsub.png -------------------------------------------------------------------------------- /code/public-cloud/gcp-state/backend.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | backend "gcs" { 3 | bucket = "pkslow-terraform" 4 | prefix = "state/gcp/pubsub" 5 | } 6 | } -------------------------------------------------------------------------------- /pictures/public-cloud/azure/create-azure-account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/azure/create-azure-account.png -------------------------------------------------------------------------------- /pictures/public-cloud/azure/state.azure-storage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/azure/state.azure-storage.png -------------------------------------------------------------------------------- /pictures/public-cloud/azure/subscription-access.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/azure/subscription-access.png -------------------------------------------------------------------------------- /pictures/public-cloud/azure/subscription-add-sp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/azure/subscription-add-sp.png -------------------------------------------------------------------------------- /pictures/00.preface/terraform-associate.certificate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/00.preface/terraform-associate.certificate.png -------------------------------------------------------------------------------- /pictures/public-cloud/azure/subscription-add-role.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/azure/subscription-add-role.png -------------------------------------------------------------------------------- /pictures/public-cloud/gcp/init-gcp-sdk.new-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/gcp/init-gcp-sdk.new-project.png -------------------------------------------------------------------------------- /pictures/public-cloud/gcp/init-gcp-sdk.new-sa-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/gcp/init-gcp-sdk.new-sa-key.png -------------------------------------------------------------------------------- /pictures/public-cloud/gcp/init-gcp-sdk.new-sa-name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/gcp/init-gcp-sdk.new-sa-name.png -------------------------------------------------------------------------------- /pictures/public-cloud/gcp/init-gcp-sdk.new-sa-role.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/gcp/init-gcp-sdk.new-sa-role.png -------------------------------------------------------------------------------- /pictures/public-cloud/gcp/terraform-gcp-pubsub.pull.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/gcp/terraform-gcp-pubsub.pull.png -------------------------------------------------------------------------------- /pictures/01.introduction/logo.azure-resource-manager.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/01.introduction/logo.azure-resource-manager.jpg -------------------------------------------------------------------------------- /pictures/public-cloud/azure/postgresql.cli.connected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/azure/postgresql.cli.connected.png -------------------------------------------------------------------------------- /pictures/public-cloud/gcp/terraform-gcs.bucket-state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/gcp/terraform-gcs.bucket-state.png -------------------------------------------------------------------------------- /pictures/01.introduction/logo.google-deployment-manager.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/01.introduction/logo.google-deployment-manager.jpg -------------------------------------------------------------------------------- /pictures/public-cloud/azure/create-azure-account.portal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/azure/create-azure-account.portal.png -------------------------------------------------------------------------------- /pictures/public-cloud/azure/create-azure-account.profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/azure/create-azure-account.profile.png -------------------------------------------------------------------------------- /pictures/public-cloud/azure/create-vm.download-ssh-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/azure/create-vm.download-ssh-key.png -------------------------------------------------------------------------------- /pictures/public-cloud/azure/postgresql.terraform.portal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/azure/postgresql.terraform.portal.png -------------------------------------------------------------------------------- /pictures/public-cloud/azure/terraform-create-resources.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/azure/terraform-create-resources.png -------------------------------------------------------------------------------- /pictures/00.preface/terraform-associate.certificate-larry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/00.preface/terraform-associate.certificate-larry.png -------------------------------------------------------------------------------- /pictures/public-cloud/azure/create-azure-account.visa-card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/azure/create-azure-account.visa-card.png -------------------------------------------------------------------------------- /pictures/public-cloud/gcp/init-gcp-sdk.new-service-account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/gcp/init-gcp-sdk.new-service-account.png -------------------------------------------------------------------------------- /pictures/public-cloud/gcp/terraform-gcp-pubsub.console-pub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/gcp/terraform-gcp-pubsub.console-pub.png -------------------------------------------------------------------------------- /pictures/public-cloud/azure/create-azure-account.with-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/azure/create-azure-account.with-github.png -------------------------------------------------------------------------------- /pictures/public-cloud/azure/create-service-principal.app-reg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/azure/create-service-principal.app-reg.png -------------------------------------------------------------------------------- /pictures/public-cloud/azure/create-service-principal.app-reg2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/azure/create-service-principal.app-reg2.png -------------------------------------------------------------------------------- /pictures/public-cloud/azure/create-service-principal.config-password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/azure/create-service-principal.config-password.png -------------------------------------------------------------------------------- /pictures/public-cloud/azure/create-service-principal.markdown-password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/azure/create-service-principal.markdown-password.png -------------------------------------------------------------------------------- /pictures/public-cloud/azure/create-service-principal.add-client-password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LarryDpk/terraform-101/HEAD/pictures/public-cloud/azure/create-service-principal.add-client-password.png -------------------------------------------------------------------------------- /code/public-cloud/azure-vm/version.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.1.3" 3 | required_providers { 4 | 5 | azurerm = { 6 | source = "hashicorp/azurerm" 7 | version = "3.38.0" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /code/public-cloud/gcp-pubsub/version.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.11" 3 | required_providers { 4 | 5 | google = { 6 | source = "hashicorp/google" 7 | version = "= 4.0.0" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /code/public-cloud/azure-postgresql/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name_prefix" { 2 | default = "pkslow-pg-fs" 3 | description = "Prefix of the resource name." 4 | } 5 | 6 | variable "location" { 7 | default = "eastus" 8 | description = "Location of the resource." 9 | } -------------------------------------------------------------------------------- /code/public-cloud/azure-postgresql/pg-fs-db.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_postgresql_flexible_server_database" "default" { 2 | name = "${var.name_prefix}-db" 3 | server_id = azurerm_postgresql_flexible_server.default.id 4 | collation = "en_US.UTF8" 5 | charset = "UTF8" 6 | } -------------------------------------------------------------------------------- /code/03.modules/random-file/variables.tf: -------------------------------------------------------------------------------- 1 | variable "prefix" { 2 | type = string 3 | default = "pkslow" 4 | description = "File name prefix" 5 | } 6 | 7 | variable "content" { 8 | type = string 9 | default = "www.pkslow.com" 10 | description = "File content" 11 | } -------------------------------------------------------------------------------- /code/public-cloud/azure-postgresql/version.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.1.3" 3 | required_providers { 4 | 5 | azurerm = { 6 | source = "hashicorp/azurerm" 7 | version = "3.38.0" 8 | } 9 | } 10 | } 11 | 12 | provider "azurerm" { 13 | features {} 14 | } -------------------------------------------------------------------------------- /code/03.modules/random-file/main.tf: -------------------------------------------------------------------------------- 1 | resource "random_string" "random" { 2 | length = 6 3 | lower = true 4 | special = false 5 | } 6 | 7 | resource "local_file" "file" { 8 | content = var.content 9 | filename = "${path.root}/.result/${var.prefix}.${random_string.random.result}.txt" 10 | } -------------------------------------------------------------------------------- /code/02.providers/version.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= v1.0.11" 3 | 4 | required_providers { 5 | local = { 6 | source = "hashicorp/local" 7 | version = "= 2.1.0" 8 | } 9 | random = { 10 | source = "hashicorp/random" 11 | version = "3.1.0" 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /code/03.modules/outputs.tf: -------------------------------------------------------------------------------- 1 | output "pkslowPileNameList" { 2 | value = module.pkslow-file.*.file_name 3 | # value = module.pkslow-file[*].file_name 4 | } 5 | 6 | output "larryFileName" { 7 | value = module.larry-file.file_name 8 | } 9 | 10 | output "larryFileResult" { 11 | value = module.echo-larry-result.stdout 12 | } -------------------------------------------------------------------------------- /code/public-cloud/azure-aks/terraform/version.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.1.3" 3 | required_providers { 4 | 5 | azurerm = { 6 | source = "hashicorp/azurerm" 7 | version = "3.38.0" 8 | } 9 | 10 | random = { 11 | source = "hashicorp/random" 12 | version = "= 3.1.0" 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /code/public-cloud/gcp-state/main.tf: -------------------------------------------------------------------------------- 1 | data "terraform_remote_state" "foo" { 2 | backend = "gcs" 3 | config = { 4 | bucket = "terraform-state" 5 | prefix = "prod" 6 | } 7 | } 8 | 9 | resource "template_file" "bar" { 10 | template = "${greeting}" 11 | 12 | vars { 13 | greeting = "${data.terraform_remote_state.foo.greeting}" 14 | } 15 | } -------------------------------------------------------------------------------- /code/public-cloud/azure-postgresql/outputs.tf: -------------------------------------------------------------------------------- 1 | output "resource_group_name" { 2 | value = azurerm_resource_group.default.name 3 | } 4 | 5 | output "azurerm_postgresql_flexible_server" { 6 | value = azurerm_postgresql_flexible_server.default.name 7 | } 8 | 9 | output "postgresql_flexible_server_database_name" { 10 | value = azurerm_postgresql_flexible_server_database.default.name 11 | } -------------------------------------------------------------------------------- /code/03.modules/version.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= v1.0.11" 3 | 4 | required_providers { 5 | local = { 6 | source = "hashicorp/local" 7 | version = "= 2.1.0" 8 | } 9 | random = { 10 | source = "hashicorp/random" 11 | version = "3.1.0" 12 | } 13 | null = { 14 | source = "hashicorp/null" 15 | version = "3.1.0" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /code/01.introduction/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= v1.0.11" 3 | 4 | required_providers { 5 | local = { 6 | source = "hashicorp/local" 7 | version = "= 2.1.0" 8 | } 9 | } 10 | } 11 | 12 | resource "local_file" "terraform-introduction" { 13 | content = "Hi guys, this is the tutorial of Terraform from pkslow.com" 14 | filename = "${path.module}/terraform-introduction-by-pkslow.txt" 15 | } -------------------------------------------------------------------------------- /code/04.states/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= v1.0.11" 3 | 4 | required_providers { 5 | local = { 6 | source = "hashicorp/local" 7 | version = "= 2.1.0" 8 | } 9 | } 10 | 11 | backend "pg" { 12 | conn_str = "postgres://pkslow:pkslow@localhost:5432/terraform?sslmode=disable" 13 | } 14 | } 15 | 16 | resource "local_file" "test-file" { 17 | content = "https://www.pkslow.com" 18 | filename = "${path.root}/terraform-guides-by-pkslow.txt" 19 | } -------------------------------------------------------------------------------- /code/03.modules/main.tf: -------------------------------------------------------------------------------- 1 | module "pkslow-file" { 2 | count = 6 3 | source = "./random-file" 4 | prefix = "pkslow-${count.index}" 5 | content = "Hi guys, this is www.pkslow.com\nBest wishes!" 6 | } 7 | 8 | module "larry-file" { 9 | source = "./random-file" 10 | prefix = "larrydpk" 11 | content = "Hi guys, this is Larry Deng!" 12 | } 13 | 14 | # external module 15 | module "echo-larry-result" { 16 | source = "matti/resource/shell" 17 | version = "1.5.0" 18 | command = "cat ${module.larry-file.file_name}" 19 | } -------------------------------------------------------------------------------- /code/public-cloud/gcp-pubsub/main.tf: -------------------------------------------------------------------------------- 1 | provider "google" { 2 | project = "pkslow" 3 | } 4 | 5 | resource "google_pubsub_topic" "pkslow-poc" { 6 | name = "pkslow-poc" 7 | } 8 | 9 | resource "google_pubsub_subscription" "pkslow-poc" { 10 | name = "pkslow-poc" 11 | topic = google_pubsub_topic.pkslow-poc.name 12 | 13 | labels = { 14 | foo = "bar" 15 | } 16 | 17 | # 20 minutes 18 | message_retention_duration = "1200s" 19 | retain_acked_messages = true 20 | 21 | ack_deadline_seconds = 20 22 | 23 | expiration_policy { 24 | ttl = "300000.5s" 25 | } 26 | retry_policy { 27 | minimum_backoff = "10s" 28 | } 29 | 30 | enable_message_ordering = true 31 | } -------------------------------------------------------------------------------- /code/public-cloud/azure-state/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.1.3" 3 | required_providers { 4 | 5 | azurerm = { 6 | source = "hashicorp/azurerm" 7 | version = "3.38.0" 8 | } 9 | local = { 10 | source = "hashicorp/local" 11 | version = "= 2.1.0" 12 | } 13 | } 14 | 15 | backend "azurerm" { 16 | resource_group_name = "pkslow-tstate-rg" 17 | storage_account_name = "pkslowtfstate" 18 | container_name = "tfstate" 19 | key = "pkslow.tfstate" 20 | } 21 | } 22 | 23 | provider "azurerm" { 24 | features {} 25 | } 26 | 27 | resource "local_file" "test-file" { 28 | content = "https://www.pkslow.com" 29 | filename = "${path.root}/terraform-guides-by-pkslow.txt" 30 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # Crash log files 9 | crash.log 10 | 11 | # Ignore any .tfvars files that are generated automatically for each Terraform run. Most 12 | # .tfvars files are managed as part of configuration and so should be included in 13 | # version control. 14 | # 15 | # example.tfvars 16 | 17 | # Ignore override files as they are usually used to override resources locally and so 18 | # are not checked in 19 | override.tf 20 | override.tf.json 21 | *_override.tf 22 | *_override.tf.json 23 | 24 | # Include override files you do wish to add to version control using negated pattern 25 | # 26 | # !example_override.tf 27 | 28 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 29 | # example: *tfplan* 30 | 31 | *.lock.hcl 32 | .result 33 | 34 | *.tfplan 35 | 36 | .idea 37 | #pictures 38 | terraform-introduction-by-pkslow.txt 39 | .DS_Store 40 | /code/public-cloud/azure-aks/terraform/azurek8s 41 | -------------------------------------------------------------------------------- /code/public-cloud/azure-aks/terraform/outputs.tf: -------------------------------------------------------------------------------- 1 | output "client_certificate" { 2 | value = azurerm_kubernetes_cluster.k8s.kube_config[0].client_certificate 3 | sensitive = true 4 | } 5 | 6 | output "client_key" { 7 | value = azurerm_kubernetes_cluster.k8s.kube_config[0].client_key 8 | sensitive = true 9 | } 10 | 11 | output "cluster_ca_certificate" { 12 | value = azurerm_kubernetes_cluster.k8s.kube_config[0].cluster_ca_certificate 13 | sensitive = true 14 | } 15 | 16 | output "cluster_password" { 17 | value = azurerm_kubernetes_cluster.k8s.kube_config[0].password 18 | sensitive = true 19 | } 20 | 21 | output "cluster_username" { 22 | value = azurerm_kubernetes_cluster.k8s.kube_config[0].username 23 | sensitive = true 24 | } 25 | 26 | output "host" { 27 | value = azurerm_kubernetes_cluster.k8s.kube_config[0].host 28 | sensitive = true 29 | } 30 | 31 | output "kube_config" { 32 | value = azurerm_kubernetes_cluster.k8s.kube_config_raw 33 | sensitive = true 34 | } 35 | 36 | output "resource_group_name" { 37 | value = azurerm_resource_group.rg.name 38 | } -------------------------------------------------------------------------------- /code/public-cloud/azure-aks/terraform/variables.tf: -------------------------------------------------------------------------------- 1 | variable "agent_count" { 2 | default = 1 3 | } 4 | 5 | # The following two variable declarations are placeholder references. 6 | # Set the values for these variable in terraform.tfvars 7 | variable "aks_service_principal_app_id" { 8 | default = "" 9 | } 10 | 11 | variable "aks_service_principal_client_secret" { 12 | default = "" 13 | } 14 | 15 | variable "cluster_name" { 16 | default = "pkslow-k8s" 17 | } 18 | 19 | variable "dns_prefix" { 20 | default = "pkslow" 21 | } 22 | 23 | # Refer to https://azure.microsoft.com/global-infrastructure/services/?products=monitor for available Log Analytics regions. 24 | variable "log_analytics_workspace_location" { 25 | default = "eastus" 26 | } 27 | 28 | variable "log_analytics_workspace_name" { 29 | default = "testLogAnalyticsWorkspaceName" 30 | } 31 | 32 | # Refer to https://azure.microsoft.com/pricing/details/monitor/ for Log Analytics pricing 33 | variable "log_analytics_workspace_sku" { 34 | default = "PerGB2018" 35 | } 36 | 37 | variable "resource_group_location" { 38 | default = "eastus" 39 | description = "Location of the resource group." 40 | } 41 | 42 | variable "resource_group_name_prefix" { 43 | default = "rg" 44 | description = "Prefix of the resource group name that's combined with a random ID so name is unique in your Azure subscription." 45 | } 46 | 47 | variable "ssh_public_key" { 48 | default = "~/.ssh/id_rsa.pub" 49 | } -------------------------------------------------------------------------------- /Terraform常用命令.md: -------------------------------------------------------------------------------- 1 | > 《Terraform 101 从入门到实践》这本小册在[南瓜慢说官方网站](https://www.pkslow.com/tags/terraform101)和[GitHub](https://github.com/LarryDpk/terraform-101)两个地方同步更新,书中的示例代码也是放在GitHub上,方便大家参考查看。 2 | 3 | --- 4 | 5 | 指定插件目录初始化: 6 | 7 | ```bash 8 | $ terraform init -plugin-dir=/Users/larry/Software/terraform/plugins 9 | $ terraform init -plugin-dir=${TERRAFORM_PLUGIN} 10 | ``` 11 | 12 | 13 | 14 | 将目录下所有Terraform文件格式化,包含子目录: 15 | 16 | ```bash 17 | $ terraform fmt -recursive 18 | ``` 19 | 20 | 21 | 22 | 非交互式apply和destroy: 23 | 24 | ```bash 25 | $ terraform apply -auto-approve 26 | $ terraform destroy -auto-approve 27 | ``` 28 | 29 | 30 | 31 | 创建一个工作区并切换: 32 | 33 | ```bash 34 | $ terraform workspace new pkslow 35 | ``` 36 | 37 | 38 | 39 | 切换到已存在的工作区: 40 | 41 | ```bash 42 | $ terraform workspace select pkslow 43 | ``` 44 | 45 | 46 | 47 | 输出变更计划到指定文件: 48 | 49 | ```bash 50 | $ terraform plan -out=pkslow.plan 51 | ``` 52 | 53 | 根据计划执行变更: 54 | 55 | ```bash 56 | $ terraform apply pkslow.plan 57 | ``` 58 | 59 | 60 | 61 | 输入变量: 62 | 63 | ```bash 64 | $ terraform apply -var="env=uat" 65 | $ terraform apply -var-file="prod.tfvars" 66 | ``` 67 | 68 | 69 | 70 | 其它: 71 | 72 | ```bash 73 | $ terraform output 74 | $ terraform console 75 | $ terraform get 76 | ``` 77 | 78 | 79 | 80 | 有用的别名: 81 | 82 | ```bash 83 | alias tfmt='terraform fmt -recursive' 84 | alias tinit='terraform init -plugin-dir=${TERRAFORM_PLUGIN}' 85 | alias tapply='terraform apply -auto-approve' 86 | alias tdestroy='terraform destroy -auto-approve' 87 | alias tplan='terraform plan' 88 | ``` 89 | 90 | -------------------------------------------------------------------------------- /code/public-cloud/azure-aks/kubernetes/azure-vote.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: azure-vote-back 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: azure-vote-back 10 | template: 11 | metadata: 12 | labels: 13 | app: azure-vote-back 14 | spec: 15 | nodeSelector: 16 | "kubernetes.io/os": linux 17 | containers: 18 | - name: azure-vote-back 19 | image: mcr.microsoft.com/oss/bitnami/redis:6.0.8 20 | env: 21 | - name: ALLOW_EMPTY_PASSWORD 22 | value: "yes" 23 | resources: 24 | requests: 25 | cpu: 100m 26 | memory: 128Mi 27 | limits: 28 | cpu: 250m 29 | memory: 256Mi 30 | ports: 31 | - containerPort: 6379 32 | name: redis 33 | --- 34 | apiVersion: v1 35 | kind: Service 36 | metadata: 37 | name: azure-vote-back 38 | spec: 39 | ports: 40 | - port: 6379 41 | selector: 42 | app: azure-vote-back 43 | --- 44 | apiVersion: apps/v1 45 | kind: Deployment 46 | metadata: 47 | name: azure-vote-front 48 | spec: 49 | replicas: 1 50 | selector: 51 | matchLabels: 52 | app: azure-vote-front 53 | template: 54 | metadata: 55 | labels: 56 | app: azure-vote-front 57 | spec: 58 | nodeSelector: 59 | "kubernetes.io/os": linux 60 | containers: 61 | - name: azure-vote-front 62 | image: mcr.microsoft.com/azuredocs/azure-vote-front:v1 63 | resources: 64 | requests: 65 | cpu: 100m 66 | memory: 128Mi 67 | limits: 68 | cpu: 250m 69 | memory: 256Mi 70 | ports: 71 | - containerPort: 80 72 | env: 73 | - name: REDIS 74 | value: "azure-vote-back" 75 | --- 76 | apiVersion: v1 77 | kind: Service 78 | metadata: 79 | name: azure-vote-front 80 | spec: 81 | type: LoadBalancer 82 | ports: 83 | - port: 80 84 | selector: 85 | app: azure-vote-front -------------------------------------------------------------------------------- /code/public-cloud/azure-aks/terraform/main.tf: -------------------------------------------------------------------------------- 1 | provider "azurerm" { 2 | features {} 3 | } 4 | 5 | # Generate random resource group name 6 | resource "random_pet" "rg_name" { 7 | prefix = var.resource_group_name_prefix 8 | } 9 | 10 | resource "azurerm_resource_group" "rg" { 11 | location = var.resource_group_location 12 | name = random_pet.rg_name.id 13 | } 14 | 15 | resource "random_id" "log_analytics_workspace_name_suffix" { 16 | byte_length = 8 17 | } 18 | 19 | resource "azurerm_log_analytics_workspace" "test" { 20 | location = var.log_analytics_workspace_location 21 | # The WorkSpace name has to be unique across the whole of azure; 22 | # not just the current subscription/tenant. 23 | name = "${var.log_analytics_workspace_name}-${random_id.log_analytics_workspace_name_suffix.dec}" 24 | resource_group_name = azurerm_resource_group.rg.name 25 | sku = var.log_analytics_workspace_sku 26 | } 27 | 28 | resource "azurerm_log_analytics_solution" "test" { 29 | location = azurerm_log_analytics_workspace.test.location 30 | resource_group_name = azurerm_resource_group.rg.name 31 | solution_name = "ContainerInsights" 32 | workspace_name = azurerm_log_analytics_workspace.test.name 33 | workspace_resource_id = azurerm_log_analytics_workspace.test.id 34 | 35 | plan { 36 | product = "OMSGallery/ContainerInsights" 37 | publisher = "Microsoft" 38 | } 39 | } 40 | 41 | resource "azurerm_kubernetes_cluster" "k8s" { 42 | location = azurerm_resource_group.rg.location 43 | name = var.cluster_name 44 | resource_group_name = azurerm_resource_group.rg.name 45 | dns_prefix = var.dns_prefix 46 | tags = { 47 | Environment = "Development" 48 | } 49 | 50 | default_node_pool { 51 | name = "agentpool" 52 | vm_size = "Standard_D2_v2" 53 | node_count = var.agent_count 54 | } 55 | linux_profile { 56 | admin_username = "ubuntu" 57 | 58 | ssh_key { 59 | key_data = file(var.ssh_public_key) 60 | } 61 | } 62 | network_profile { 63 | network_plugin = "kubenet" 64 | load_balancer_sku = "standard" 65 | } 66 | service_principal { 67 | client_id = var.aks_service_principal_app_id 68 | client_secret = var.aks_service_principal_client_secret 69 | } 70 | } -------------------------------------------------------------------------------- /code/public-cloud/azure-vm/main.tf: -------------------------------------------------------------------------------- 1 | provider "azurerm" { 2 | features {} 3 | } 4 | 5 | variable "prefix" { 6 | default = "pkslow-azure" 7 | } 8 | 9 | resource "azurerm_resource_group" "example" { 10 | name = "${var.prefix}-resources" 11 | location = "West Europe" 12 | } 13 | 14 | resource "azurerm_virtual_network" "main" { 15 | name = "${var.prefix}-network" 16 | address_space = ["10.0.0.0/16"] 17 | location = azurerm_resource_group.example.location 18 | resource_group_name = azurerm_resource_group.example.name 19 | } 20 | 21 | resource "azurerm_subnet" "internal" { 22 | name = "internal" 23 | resource_group_name = azurerm_resource_group.example.name 24 | virtual_network_name = azurerm_virtual_network.main.name 25 | address_prefixes = ["10.0.2.0/24"] 26 | } 27 | 28 | resource "azurerm_network_interface" "main" { 29 | name = "${var.prefix}-nic" 30 | location = azurerm_resource_group.example.location 31 | resource_group_name = azurerm_resource_group.example.name 32 | 33 | ip_configuration { 34 | name = "testconfiguration1" 35 | subnet_id = azurerm_subnet.internal.id 36 | private_ip_address_allocation = "Dynamic" 37 | } 38 | } 39 | 40 | resource "azurerm_virtual_machine" "main" { 41 | name = "${var.prefix}-vm" 42 | location = azurerm_resource_group.example.location 43 | resource_group_name = azurerm_resource_group.example.name 44 | network_interface_ids = [azurerm_network_interface.main.id] 45 | vm_size = "Standard_DS1_v2" 46 | 47 | # Uncomment this line to delete the OS disk automatically when deleting the VM 48 | # delete_os_disk_on_termination = true 49 | 50 | # Uncomment this line to delete the data disks automatically when deleting the VM 51 | # delete_data_disks_on_termination = true 52 | 53 | storage_image_reference { 54 | publisher = "Canonical" 55 | offer = "0001-com-ubuntu-server-jammy" 56 | sku = "22_04-lts" 57 | version = "22.04.202301100" 58 | } 59 | storage_os_disk { 60 | name = "myosdisk1" 61 | caching = "ReadWrite" 62 | create_option = "FromImage" 63 | managed_disk_type = "Standard_LRS" 64 | } 65 | os_profile { 66 | computer_name = "hostname" 67 | admin_username = "larry" 68 | admin_password = "Password1234!" 69 | } 70 | os_profile_linux_config { 71 | disable_password_authentication = false 72 | } 73 | tags = { 74 | environment = "staging" 75 | } 76 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > 《Terraform 101 从入门到实践》这本小册在[南瓜慢说官方网站](https://www.pkslow.com/tags/terraform101)和[GitHub](https://github.com/LarryDpk/terraform-101)两个地方同步更新,书中的示例代码也是放在GitHub上,方便大家参考查看。 2 | 3 | --- 4 | 5 | # Terraform 101 从入门到实践 6 | Terraform作为基础设施即代码(Infrastructure as Code,很简称IaC)的事实标准,非常值得大家学习。我是工作中会使用公有云,所以需要经常使用Terraform作为IaC工具以实现自动化部署;也花时间考取了**Terraform Associate**的证书。所以对它的使用我还是有一些经验的。但Terraform本身发展是比较快的,国内的资料也相对较少,所以我整理了我的学习心得,希望可以帮助到大家。 7 | 8 | 因此,我做了一个决定,将知识点整理成小册,叫《Terraform 101 从入门到实践》。该小册会不断增加和完善内容,所以初期会有很多不完美的地方。如果大家有问题可以提Issue,但前期不会处理。**因为工作变动和育儿的原因,我需要学习和适应,没有ETA,慢慢更新吧**。 9 | 10 | 但Terraform的基本概念是已经介绍了的,了解与入门是够用了。我会尽量在工作之余、带娃之余、睡觉之余挤时间完成。这篇文章也算是立个Flag,并自我监督吧。 11 | 12 | 13 | 14 | 15 | 16 | 如果大家觉得不错,可以好心给个STAR支持一下哦。你的鼓励,是我的动力。 17 | 18 | 19 | **GitHub目录**: 20 | 21 | - [前言](https://github.com/LarryDpk/terraform-101/blob/main/README.md) 22 | - [第一章 Terraform初相识](https://github.com/LarryDpk/terraform-101/blob/main/01.Terraform初相识.md) 23 | - [第二章 Providers插件管理](https://github.com/LarryDpk/terraform-101/blob/main/02.Providers插件管理.md) 24 | - [第三章 Modules模块化](https://github.com/LarryDpk/terraform-101/blob/main/03.Modules模块化.md) 25 | - [第四章 States状态管理](https://github.com/LarryDpk/terraform-101/blob/main/04.States状态管理.md) 26 | - [第五章 HCL语法](https://github.com/LarryDpk/terraform-101/blob/main/05.HCL语法.md) 27 | - [Functions函数](https://github.com/LarryDpk/terraform-101/blob/main/Functions函数.md) 28 | - [Terraform常用命令](https://github.com/LarryDpk/terraform-101/blob/main/Terraform常用命令.md) 29 | - [Terraform在公有云GCP上的应用](https://github.com/LarryDpk/terraform-101/blob/main/Terraform在公有云GCP上的应用.md) 30 | - [Terraform在公有云Azure上的应用](https://github.com/LarryDpk/terraform-101/blob/main/Terraform在公有云Azure上的应用.md) 31 | - Terraform问题定位与分析(未开始) 32 | - 插件开发(未开始) 33 | - 最佳实践(未开始) 34 | - 开发套件(未开始) 35 | 36 | 37 | **博客目录**: 38 | 39 | - [前言](https://www.pkslow.com/archives/terraform-101-preface) 40 | - [第一章 Terraform初相识](https://www.pkslow.com/archives/terraform-101-introduction) 41 | - [第二章 Providers插件管理](https://www.pkslow.com/archives/terraform-101-providers) 42 | - [第三章 Modules模块化](https://www.pkslow.com/archives/terraform-101-modules) 43 | - [第四章 States状态管理](https://www.pkslow.com/archives/terraform-101-states) 44 | - [第五章 HCL语法](https://www.pkslow.com/archives/terraform-101-hcl) 45 | - [Functions函数](https://www.pkslow.com/archives/terraform-101-functions) 46 | - [Terraform常用命令](https://www.pkslow.com/archives/terraform-101-commands) 47 | - [Terraform在公有云GCP上的应用](https://www.pkslow.com/archives/terraform-101-gcp) 48 | - [Terraform在公有云Azure上的应用](https://www.pkslow.com/archives/terraform-101-azure) 49 | --- 50 | 51 | 52 | 最后,附上我的Terraform证书: 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /code/public-cloud/azure-postgresql/main.tf: -------------------------------------------------------------------------------- 1 | resource "random_pet" "rg-name" { 2 | prefix = var.name_prefix 3 | } 4 | 5 | resource "azurerm_resource_group" "default" { 6 | name = random_pet.rg-name.id 7 | location = var.location 8 | } 9 | 10 | resource "azurerm_virtual_network" "default" { 11 | name = "${var.name_prefix}-vnet" 12 | location = azurerm_resource_group.default.location 13 | resource_group_name = azurerm_resource_group.default.name 14 | address_space = ["10.0.0.0/16"] 15 | } 16 | 17 | resource "azurerm_network_security_group" "default" { 18 | name = "${var.name_prefix}-nsg" 19 | location = azurerm_resource_group.default.location 20 | resource_group_name = azurerm_resource_group.default.name 21 | 22 | security_rule { 23 | name = "test123" 24 | priority = 100 25 | direction = "Inbound" 26 | access = "Allow" 27 | protocol = "Tcp" 28 | source_port_range = "*" 29 | destination_port_range = "*" 30 | source_address_prefix = "*" 31 | destination_address_prefix = "*" 32 | } 33 | } 34 | 35 | resource "azurerm_subnet" "default" { 36 | name = "${var.name_prefix}-subnet" 37 | virtual_network_name = azurerm_virtual_network.default.name 38 | resource_group_name = azurerm_resource_group.default.name 39 | address_prefixes = ["10.0.2.0/24"] 40 | service_endpoints = ["Microsoft.Storage"] 41 | 42 | delegation { 43 | name = "fs" 44 | 45 | service_delegation { 46 | name = "Microsoft.DBforPostgreSQL/flexibleServers" 47 | 48 | actions = [ 49 | "Microsoft.Network/virtualNetworks/subnets/join/action", 50 | ] 51 | } 52 | } 53 | } 54 | 55 | resource "azurerm_subnet_network_security_group_association" "default" { 56 | subnet_id = azurerm_subnet.default.id 57 | network_security_group_id = azurerm_network_security_group.default.id 58 | } 59 | 60 | resource "azurerm_private_dns_zone" "default" { 61 | name = "${var.name_prefix}-pdz.postgres.database.azure.com" 62 | resource_group_name = azurerm_resource_group.default.name 63 | 64 | depends_on = [azurerm_subnet_network_security_group_association.default] 65 | } 66 | 67 | resource "azurerm_private_dns_zone_virtual_network_link" "default" { 68 | name = "${var.name_prefix}-pdzvnetlink.com" 69 | private_dns_zone_name = azurerm_private_dns_zone.default.name 70 | virtual_network_id = azurerm_virtual_network.default.id 71 | resource_group_name = azurerm_resource_group.default.name 72 | } 73 | 74 | resource "azurerm_postgresql_flexible_server" "default" { 75 | name = "${var.name_prefix}-server" 76 | resource_group_name = azurerm_resource_group.default.name 77 | location = azurerm_resource_group.default.location 78 | version = "13" 79 | delegated_subnet_id = azurerm_subnet.default.id 80 | private_dns_zone_id = azurerm_private_dns_zone.default.id 81 | administrator_login = "pguser" 82 | administrator_password = "QAZwsx123" 83 | zone = "1" 84 | storage_mb = 32768 85 | sku_name = "GP_Standard_D2s_v3" 86 | backup_retention_days = 7 87 | 88 | depends_on = [azurerm_private_dns_zone_virtual_network_link.default] 89 | } -------------------------------------------------------------------------------- /03.Modules模块化.md: -------------------------------------------------------------------------------- 1 | > 《Terraform 101 从入门到实践》这本小册在[南瓜慢说官方网站](https://www.pkslow.com/tags/terraform101)和[GitHub](https://github.com/LarryDpk/terraform-101)两个地方同步更新,书中的示例代码也是放在GitHub上,方便大家参考查看。 2 | 3 | --- 4 | 5 | 6 | 7 | 8 | # 模块的概念 9 | 10 | 模块化是Terraform实现代码重用的方式。模块可以理解为一个包含多个资源的容器模板。封装好之后,可以给大家使用。也可以理解为代码中的函数或方法,它接收入参,经过一些声明式的调用后,输出一些结果变量。 11 | 12 | 从Terraform的代码层面来看,模块其实就是一个包含多个.tf或.tf.json文件的目录。任何一个Terraform项目,都是一个目录,所以也都是一个模块,我们把它称为根模块(Root Module)。而在它目录下的其它模块,都是子模块。我们可以调用多个模块,也可以多次调用同一个子模块。在子模块中,也可以调用其它模块。这些特点,与函数无异。 13 | 14 | 15 | 16 | 调用模块有两种方式,一种是在当前项目定义一个模块,另一种是引入外部的模块。而外部模块的方式也很多种,如Git的仓库、压缩文件等。 17 | 18 | 19 | 20 | # 定义并使用模块 21 | 22 | 我们先来使用第一种方式,引用当前项目中的模块。 23 | 24 | 子模块的功能很简单,创建一个文件,文件名有随机字符串,以避免冲突。写入文件的内容可以通过参数指定。 25 | 26 | 子模块: 27 | 28 | 定义入参:创建一个文件叫variables.tf,专门用来定义入参: 29 | 30 | ```hcl 31 | variable "prefix" { 32 | type = string 33 | default = "pkslow" 34 | description = "File name prefix" 35 | } 36 | 37 | variable "content" { 38 | type = string 39 | default = "www.pkslow.com" 40 | description = "File content" 41 | } 42 | ``` 43 | 44 | 这里输入有两个变量,都是字符串类型,分别是文件名前缀prefix和文件内容context。 45 | 46 | 47 | 48 | 定义模块功能,主要配置这个模块用管理的资源,一般会放在main.tf文件中,内容如下: 49 | 50 | ```hcl 51 | resource "random_string" "random" { 52 | length = 6 53 | lower = true 54 | special = false 55 | } 56 | 57 | resource "local_file" "file" { 58 | content = var.content 59 | filename = "${path.root}/${var.prefix}.${random_string.random.result}.txt" 60 | } 61 | ``` 62 | 63 | 这里定义了两个resource,第一个是生成6位的随机字符串。第二个是生成一个文件,第二个resource使用了输入参数,还使用了第一个资源生成的结果。所以第二个resource是依赖于第一个的。输入的变量引用方式为`var.xxx`。 64 | 65 | 66 | 67 | 定义返回值: 68 | 69 | 可以不需要返回值,也可以定义一个或多个返回值。创建一个outputs.tf文件,内容如下: 70 | 71 | ```hcl 72 | output "file_name" { 73 | value = local_file.file.filename 74 | } 75 | ``` 76 | 77 | 它返回的是前面第二个resource中的值。 78 | 79 | 80 | 81 | 现在,模块random-file已经定义完成了。现在我们在根模块调用这个子模块。代码如下: 82 | 83 | ```hcl 84 | module "local-file" { 85 | source = "./random-file" 86 | prefix = "pkslow" 87 | content = "Hi guys, this is www.pkslow.com\nBest wishes!" 88 | } 89 | ``` 90 | 91 | 这个source是被调用模块的地址。`prefix`和`content`都是入参,之前已经定义了。 92 | 93 | 94 | 95 | 在根模块也可以定义输出变量: 96 | 97 | ```hcl 98 | output "fileName" { 99 | value = module.local-file.file_name 100 | } 101 | ``` 102 | 103 | 这里直接输出子模块的文件名,也就是子模块的返回变量file_name。 104 | 105 | 106 | 107 | `apply`后通过`terraform output`查看输出: 108 | 109 | ```bash 110 | $ terraform output 111 | fileName = "./pkslow.B2UwmR.txt" 112 | ``` 113 | 114 | 115 | 116 | # 多个block调用同一个module 117 | 118 | 我们说过模块是为了实现代码复用,Terraform允许一个模块被多次调用。我们修改根模块的调用代码: 119 | 120 | ```hcl 121 | module "pkslow-file" { 122 | source = "./random-file" 123 | prefix = "pkslow" 124 | content = "Hi guys, this is www.pkslow.com\nBest wishes!" 125 | } 126 | 127 | module "larry-file" { 128 | source = "./random-file" 129 | prefix = "larrydpk" 130 | content = "Hi guys, this is Larry Deng!" 131 | } 132 | ``` 133 | 134 | 这里两个调用的source都是一样的,都调用了`random-file`这个模块,只是入参不同。 135 | 136 | 根模块的输出也修改一下: 137 | 138 | ```hcl 139 | output "pkslowPileName" { 140 | value = module.pkslow-file.file_name 141 | } 142 | 143 | output "larryFileName" { 144 | value = module.larry-file.file_name 145 | } 146 | ``` 147 | 148 | 149 | 150 | 执行`apply`后output输出结果为: 151 | 152 | ```bash 153 | $ terraform output 154 | larryFileName = "./larrydpk.txoV34.txt" 155 | pkslowPileName = "./pkslow.WnJVMm.txt" 156 | ``` 157 | 158 | 159 | 160 | # 循环调用一个module 161 | 162 | ## count方式 163 | 164 | 多次调用一个模块还有另一种方式就是循环调用,通过`count`来实现,具体如下: 165 | 166 | ```hcl 167 | module "pkslow-file" { 168 | count = 6 169 | source = "./random-file" 170 | prefix = "pkslow-${count.index}" 171 | content = "Hi guys, this is www.pkslow.com\nBest wishes!" 172 | } 173 | ``` 174 | 175 | 这里会调用6次子模块`random-file`,下标索引为`count.index`,它是从0开始的索引。 176 | 177 | 因此,执行后,会生成以下6个文件: 178 | 179 | ```bash 180 | pkslow-0.JBDuhH.txt 181 | pkslow-1.Z6QmPV.txt 182 | pkslow-2.PlCK5u.txt 183 | pkslow-3.a70sWN.txt 184 | pkslow-4.UnxYue.txt 185 | pkslow-5.8bSNxg.txt 186 | ``` 187 | 188 | 189 | 190 | 这里根模块的输出就需要修改了,它成了一个List,通过`*`引用所有元素: 191 | 192 | ```hcl 193 | output "pkslowPileNameList" { 194 | value = module.pkslow-file.*.file_name 195 | } 196 | ``` 197 | 198 | 199 | 200 | ## for each方式 201 | 202 | 通过`for_each`也可以实现循环调用: 203 | 204 | Map的情况: 205 | 206 | ```hcl 207 | resource "azurerm_resource_group" "rg" { 208 | for_each = { 209 | a_group = "eastus" 210 | another_group = "westus2" 211 | } 212 | name = each.key 213 | location = each.value 214 | } 215 | ``` 216 | 217 | 218 | 219 | Set的情况: 220 | 221 | ```hcl 222 | resource "aws_iam_user" "the-accounts" { 223 | for_each = toset( ["Todd", "James", "Alice", "Dottie"] ) 224 | name = each.key 225 | } 226 | ``` 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | # 引用外部模块 235 | 236 | 除了在本项目中定义并引用模块之外,还可以引用外部的模块。在官方的仓库中已经有非常多的可重用的模块了,可以到上面查找:https://registry.terraform.io/browse/modules 237 | 238 | 239 | 240 | 比如我引用了( https://registry.terraform.io/modules/matti/resource/shell/latest )这个模块: 241 | 242 | ```hcl 243 | module "echo-larry-result" { 244 | source = "matti/resource/shell" 245 | version = "1.5.0" 246 | command = "cat ${module.larry-file.file_name}" 247 | } 248 | ``` 249 | 250 | 执行`terraform get`会从仓库下载模块: 251 | 252 | ```bash 253 | $ terraform get 254 | Downloading matti/resource/shell 1.5.0 for echo-larry-result... 255 | - echo-larry-result in .terraform/modules/echo-larry-result 256 | - larry-file in random-file 257 | - pkslow-file in random-file 258 | ``` 259 | 260 | 在`.modules`目录下可以查看模块内容。 261 | 262 | 263 | 264 | 这个模块可以执行shell命令,并返回结果。我这里执行的命令是读取之前生成文件的内容。输出调用结果: 265 | 266 | ```hscl 267 | output "larryFileResult" { 268 | value = module.echo-larry-result.stdout 269 | } 270 | ``` 271 | 272 | 执行结果如下: 273 | 274 | ```bash 275 | larryFileName = "./.result/larrydpk.GfgMyh.txt" 276 | larryFileResult = "Hi guys, this is Larry Deng!" 277 | ``` 278 | 279 | 280 | 281 | # 模块来源 282 | 283 | 引入模块的来源很多: 284 | 285 | - 本地目录 286 | - Terraform官方仓库 287 | - GitHub或其它Git仓库 288 | - Bitbucket 289 | - HTTP URLs 290 | - S3 Buckets 291 | - GCS Bucket 292 | 293 | 非常方便。我们已经介绍过比较常用的前两种了,其它更多细节可以参考:https://www.terraform.io/docs/language/modules/sources.html 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | --- 302 | 303 | 参考: 304 | 305 | https://www.terraform.io/docs/language/meta-arguments/count.html 306 | 307 | https://stackoverflow.com/questions/64989080/terraform-modules-output-from-for-each 308 | 309 | 310 | 311 | -------------------------------------------------------------------------------- /02.Providers插件管理.md: -------------------------------------------------------------------------------- 1 | > 《Terraform 101 从入门到实践》这本小册在[南瓜慢说官方网站](https://www.pkslow.com/tags/terraform101)和[GitHub](https://github.com/LarryDpk/terraform-101)两个地方同步更新,书中的示例代码也是放在GitHub上,方便大家参考查看。 2 | 3 | --- 4 | 5 | 6 | 7 | 8 | 9 | > 不怕出身低,行行出状元。 10 | 11 | 12 | 13 | # 插件 14 | 15 | Terraform可以对多种平台的多种资源进行管理,这个是通过插件来实现的。 16 | 17 | 这里的插件,在Terraform的世界也叫Providers,也是一个个可执行文件。不同的插件完成不同的功能,对接AWS,就要使用AWS的插件;对接GCP,就要用GCP的插件。 18 | 19 | 当我们通过`terraform init`初始化一个项目时,Terraform就会根据配置帮我们下载插件。在我们执行apply的时候,就会调用这些插件实现对应的资源管理。 20 | 21 | 我们可以到官方仓库( https://registry.terraform.io/browse/providers )去搜有什么插件可用,这里有极其丰富的插件,也有详细的使用说明: 22 | 23 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/02.providers/providers.official-site.png) 24 | 25 | 26 | 27 | 接下来,我们就插件探讨几个问题: 28 | 29 | - 怎么指定下载哪些插件和版本号? 30 | - 从哪里下载? 31 | - 下载到什么地方? 32 | - 没有对插件库有访问权限的环境下怎么处理? 33 | - 是否每个项目都要下载相同的插件? 34 | 35 | 36 | 37 | # 指定下载哪些插件和版本 38 | 39 | 40 | 41 | Terraform是通过解析required_providers知道需要哪些插件,一般习惯是定义一个verion.tf文件,把相关配置都放在这个文件里,比如: 42 | 43 | ```hcl 44 | terraform { 45 | required_version = "= v1.0.11" 46 | 47 | required_providers { 48 | local = { 49 | source = "hashicorp/local" 50 | version = "= 2.1.0" 51 | } 52 | random = { 53 | source = "hashicorp/random" 54 | version = "3.1.0" 55 | } 56 | } 57 | } 58 | ``` 59 | 60 | 这个文件定义了Terraform核心组件的版本,还定义了local和random插件及其版本号。上面指定Terraform版本为1.0.11,local版本为2.1.0,random版本为3.1.0。 61 | 62 | 我们看这里的版本号有两个等于`=`号,会不会觉得奇怪?其实这是HCL语言的一个特性,除了`=`号,还可以是`>`、`<=`等,这样可以指定版本范围,而不只是某个特定版本。 63 | 64 | 65 | 66 | # 从哪里下载 67 | 68 | 可以通过命令`terraform providers`查看当前项目配置的插件是从哪里下载的。如下: 69 | 70 | ```bash 71 | $ terraform providers 72 | 73 | Providers required by configuration: 74 | . 75 | ├── provider[registry.terraform.io/hashicorp/random] 3.1.0 76 | └── provider[registry.terraform.io/hashicorp/local] 2.1.0 77 | ``` 78 | 79 | 默认是从官方的公共仓库`registry.terraform.io`下载的。 80 | 81 | 82 | 83 | 如果需要指定其它仓库,代码如下: 84 | 85 | ```hcl 86 | terraform { 87 | required_version = "= v1.0.11" 88 | 89 | required_providers { 90 | local = { 91 | source = "hashicorp/local" 92 | version = "= 2.1.0" 93 | } 94 | random = { 95 | source = "hashicorp/random" 96 | version = "3.1.0" 97 | } 98 | pkslowcloud = { 99 | source = "registry.pkslow.com/examplecorp/pkslowcloud" 100 | version = "0.1.0" 101 | } 102 | } 103 | } 104 | ``` 105 | 106 | 这里`pkslowcloud`就是使用自定义的仓库地址,执行providers命令如下: 107 | 108 | ```bash 109 | $ terraform providers 110 | 111 | Providers required by configuration: 112 | . 113 | ├── provider[registry.terraform.io/hashicorp/local] 2.1.0 114 | ├── provider[registry.terraform.io/hashicorp/random] 3.1.0 115 | └── provider[registry.pkslow.com/examplecorp/pkslowcloud] 0.1.0 116 | ``` 117 | 118 | 注意:`pkslowcloud`实际不存在,大家不必尝试下载使用。 119 | 120 | 121 | 122 | # 下载到什么地方 123 | 124 | 执行`terraform init`进行初始化,就会下载插件: 125 | 126 | ```bash 127 | $ terraform init 128 | 129 | Initializing the backend... 130 | 131 | Initializing provider plugins... 132 | - Finding hashicorp/random versions matching "3.1.0"... 133 | - Finding hashicorp/local versions matching "2.1.0"... 134 | - Installing hashicorp/random v3.1.0... 135 | - Installed hashicorp/random v3.1.0 (signed by HashiCorp) 136 | - Installing hashicorp/local v2.1.0... 137 | - Installed hashicorp/local v2.1.0 (signed by HashiCorp) 138 | ``` 139 | 140 | 执行完init命令后,当前工作目录就会有一个`.terraform`文件夹,这里就放了插件的程序。目录结构如下: 141 | 142 | ```bash 143 | $ tree -a 144 | . 145 | ├── .terraform 146 | │   └── providers 147 | │   └── registry.terraform.io 148 | │   └── hashicorp 149 | │   ├── local 150 | │   │   └── 2.1.0 151 | │   │   └── darwin_amd64 152 | │   │   └── terraform-provider-local_v2.1.0_x5 153 | │   └── random 154 | │   └── 3.1.0 155 | │   └── darwin_amd64 156 | │   └── terraform-provider-random_v3.1.0_x5 157 | ``` 158 | 159 | 160 | 161 | # 没有网络环境怎么办 162 | 163 | 在有些情况下,并不能直接访问Terraform的公共仓库去下载插件,如果可以从其它地方复制一份插件,并可以使用,那岂不是美哉?Terraform已经考虑了这种需求。 164 | 165 | 首先它支持有网络环境的机器把当前目录的插件复制到特定目录,命令如下: 166 | 167 | ```bash 168 | $ terraform providers mirror /Users/larry/Software/terraform/plugins 169 | - Mirroring hashicorp/local... 170 | - Selected v2.1.0 to meet constraints 2.1.0 171 | - Downloading package for darwin_amd64... 172 | - Package authenticated: signed by HashiCorp 173 | - Mirroring hashicorp/random... 174 | - Selected v3.1.0 to meet constraints 3.1.0 175 | - Downloading package for darwin_amd64... 176 | - Package authenticated: signed by HashiCorp 177 | ``` 178 | 179 | 180 | 181 | 查看一下目录结构,Terraform会打包好插件为zip文件: 182 | 183 | ```bash 184 | $ tree -a /Users/larry/Software/terraform/plugins 185 | /Users/larry/Software/terraform/plugins-localdisk 186 | └── registry.terraform.io 187 | └── hashicorp 188 | ├── local 189 | │   ├── 2.1.0.json 190 | │   ├── index.json 191 | │   └── terraform-provider-local_2.1.0_darwin_amd64.zip 192 | └── random 193 | ├── 3.1.0.json 194 | ├── index.json 195 | └── terraform-provider-random_3.1.0_darwin_amd64.zip 196 | ``` 197 | 198 | 199 | 200 | 下次我们可以指定插件目录实现复用: 201 | 202 | ```bash 203 | $ terraform init -plugin-dir=/Users/larry/Software/terraform/plugins 204 | 205 | Initializing the backend... 206 | 207 | Initializing provider plugins... 208 | - Reusing previous version of hashicorp/random from the dependency lock file 209 | - Reusing previous version of hashicorp/local from the dependency lock file 210 | - Using previously-installed hashicorp/random v3.1.0 211 | - Using previously-installed hashicorp/local v2.1.0 212 | ``` 213 | 214 | 看日志可以看到,Terraform不再下载,而是重用插件。 215 | 216 | 217 | 218 | 执行完命令init后,再查看`terraform version`,则会显示插件的版本: 219 | 220 | ```bash 221 | $ terraform version 222 | Terraform v1.0.11 223 | on darwin_amd64 224 | + provider registry.terraform.io/hashicorp/local v2.1.0 225 | + provider registry.terraform.io/hashicorp/random v3.1.0 226 | ``` 227 | 228 | 229 | 230 | 231 | 232 | Terraform对于这种插件目录重用的支持,不只是zip包,二进制也是支持的,但对应的目录结果有点不一样。这里不展开介绍了。 233 | 234 | 235 | 236 | # 常用插件介绍 237 | 238 | 常用插件有: 239 | 240 | - local 241 | - random: https://registry.terraform.io/providers/hashicorp/random/latest 242 | - template 243 | - gcp 244 | - aws 245 | - azure 246 | 247 | 248 | 249 | Null: https://registry.terraform.io/providers/hashicorp/null/latest 250 | 251 | http: https://registry.terraform.io/providers/hashicorp/http/latest 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | -------------------------------------------------------------------------------- /Terraform在公有云GCP上的应用.md: -------------------------------------------------------------------------------- 1 | > 《Terraform 101 从入门到实践》这本小册在[南瓜慢说官方网站](https://www.pkslow.com/tags/terraform101)和[GitHub](https://github.com/LarryDpk/terraform-101)两个地方同步更新,书中的示例代码也是放在GitHub上,方便大家参考查看。 2 | 3 | --- 4 | 5 | Terraform支持的公有云有很多,如AWS、Azure、Google、Alibaba等。将Terraform应用于公有云,才最能发挥其强大的功能。 6 | 7 | # 初始化GCP项目 8 | 9 | 10 | 11 | ## 创建一个新项目 12 | 首先我们需要初始化一个GCP项目。GCP给开发者提供了免费试用的服务,我们可以在不花钱的情况下学习GCP的功能。 13 | 14 | 要使用GCP,我们需要创建一个项目,它所有的资源都是在项目之下管理的: 15 | 16 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/gcp/init-gcp-sdk.new-project.png) 17 | 18 | 19 | 20 | ## 创建Service Account 21 | 22 | 在实际开发中,我们不能使用自己的账号在做操作,最好的方式是创建一个服务账号(Service Account),这应该也是所有云平台都推荐的方式。创建位置如下: 23 | 24 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/gcp/init-gcp-sdk.new-service-account.png) 25 | 26 | 27 | 28 | 输入账号名字: 29 | 30 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/gcp/init-gcp-sdk.new-sa-name.png) 31 | 32 | 33 | 34 | 选择角色,为了方便,我直接选择Owner,会拥有所有权限,但实际应用肯定不能这样,要做好隔离: 35 | 36 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/gcp/init-gcp-sdk.new-sa-role.png) 37 | 38 | 39 | 40 | 41 | 42 | ## 创建密钥文件 43 | 44 | 对于Service Account,不是通过用户名密码来授权的,而是通过密钥文件,创建如下: 45 | 46 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/gcp/init-gcp-sdk.new-sa-key.png) 47 | 48 | 49 | 50 | 选择新建一个密钥,并格式为json。创建后,会自动下载key文件。 51 | 52 | 53 | 54 | ## 设置gcloud SDK 55 | 56 | Key文件拿到后,我们可以设置环境变量:**GOOGLE_APPLICATION_CREDENTIALS**: 57 | 58 | ```bash 59 | $ export GOOGLE_APPLICATION_CREDENTIALS=/Users/larry/Software/google-cloud-sdk/pkslow-admin-for-all.json 60 | ``` 61 | 62 | 63 | 64 | 激活Service Account: 65 | 66 | ```bash 67 | $ gcloud auth activate-service-account admin-for-all@pkslow.iam.gserviceaccount.com --key-file=${GOOGLE_APPLICATION_CREDENTIALS} 68 | ``` 69 | 70 | 71 | 72 | 设置SDK的项目ID: 73 | 74 | ```bash 75 | $ gcloud config set project pkslow 76 | ``` 77 | 78 | 79 | 80 | 检查一下设置是否正确: 81 | 82 | ```bash 83 | $ gcloud auth list 84 | Credentialed Accounts 85 | ACTIVE ACCOUNT 86 | * admin-for-all@pkslow.iam.gserviceaccount.com 87 | 88 | To set the active account, run: 89 | $ gcloud config set account `ACCOUNT` 90 | 91 | 92 | $ gcloud config list 93 | [core] 94 | account = admin-for-all@pkslow.iam.gserviceaccount.com 95 | disable_usage_reporting = True 96 | project = pkslow 97 | 98 | Your active configuration is: [default] 99 | ``` 100 | 101 | 102 | 103 | ## 使用gcloud创建Pub/Sub 104 | 105 | SDK设置好后,就可以使用了,我们使用它来创建Pub/Sub试试。创建主题和订阅: 106 | 107 | ```bash 108 | $ gcloud pubsub topics create pkslow-test 109 | Created topic [projects/pkslow/topics/pkslow-test]. 110 | 111 | $ gcloud pubsub subscriptions create pkslow-sub --topic=pkslow-test 112 | Created subscription [projects/pkslow/subscriptions/pkslow-sub]. 113 | ``` 114 | 115 | 116 | 117 | 检查是否创建成功: 118 | 119 | ```bash 120 | $ gcloud pubsub topics list 121 | --- 122 | name: projects/pkslow/topics/pkslow-test 123 | 124 | 125 | $ gcloud pubsub subscriptions list 126 | --- 127 | ackDeadlineSeconds: 10 128 | expirationPolicy: 129 | ttl: 2678400s 130 | messageRetentionDuration: 604800s 131 | name: projects/pkslow/subscriptions/pkslow-sub 132 | pushConfig: {} 133 | topic: projects/pkslow/topics/pkslow-test 134 | ``` 135 | 136 | 137 | 138 | 在浏览器查看,发现已经成功创建了: 139 | 140 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/gcp/init-gcp-sdk.pubsub.png) 141 | 142 | --- 143 | 144 | # Terraform创建Pub/Sub 145 | 146 | ## 下载Terraform插件 147 | 148 | 我们需要安装GCP的Terraform插件来管理GCP资源: 149 | 150 | ```bash 151 | # 设置插件目录 152 | $ export TERRAFORM_PLUGIN=/Users/larry/Software/terraform/plugins 153 | # 创建目录 154 | $ mkdir -p ${TERRAFORM_PLUGIN}/registry.terraform.io/hashicorp/google/4.0.0/darwin_amd64 155 | $ cd ${TERRAFORM_PLUGIN}/registry.terraform.io/hashicorp/google/4.0.0/darwin_amd64 156 | # 下载 157 | $ wget https://releases.hashicorp.com/terraform-provider-google/4.0.0/terraform-provider-google_4.0.0_darwin_amd64.zip 158 | # 解压 159 | $ unzip terraform-provider-google_4.0.0_darwin_amd64.zip 160 | ``` 161 | 162 | 163 | 164 | ## 准备Terraform代码 165 | 166 | 需要提供Terraform代码理管理Pub/Sub,更多细节请参考: [Terrafrom GCP](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/pubsub_subscription). 167 | 168 | 169 | 170 | 版本文件version.tf: 171 | 172 | ```hcl 173 | terraform { 174 | required_version = "= 1.0.11" 175 | required_providers { 176 | 177 | google = { 178 | source = "hashicorp/google" 179 | version = "= 4.0.0" 180 | } 181 | } 182 | } 183 | ``` 184 | 185 | 186 | 187 | 主文件main.tf: 188 | 189 | ```hcl 190 | provider "google" { 191 | project = "pkslow" 192 | } 193 | 194 | resource "google_pubsub_topic" "pkslow-poc" { 195 | name = "pkslow-poc" 196 | } 197 | 198 | resource "google_pubsub_subscription" "pkslow-poc" { 199 | name = "pkslow-poc" 200 | topic = google_pubsub_topic.pkslow-poc.name 201 | 202 | labels = { 203 | foo = "bar" 204 | } 205 | 206 | # 20 minutes 207 | message_retention_duration = "1200s" 208 | retain_acked_messages = true 209 | 210 | ack_deadline_seconds = 20 211 | 212 | expiration_policy { 213 | ttl = "300000.5s" 214 | } 215 | retry_policy { 216 | minimum_backoff = "10s" 217 | } 218 | 219 | enable_message_ordering = true 220 | } 221 | ``` 222 | 223 | 224 | 225 | ## 初始化和变更 226 | 227 | 指定插件目录初始化: 228 | 229 | ```bash 230 | $ terraform init -plugin-dir=${TERRAFORM_PLUGIN} 231 | ``` 232 | 233 | 234 | 235 | 使变更生效,就会在GCP上创建对应的资源: 236 | 237 | ```bash 238 | $ terraform apply -auto-approve 239 | ``` 240 | 241 | 242 | 243 | 如果没有发生错误,则意味着创建成功,我们检查一下: 244 | 245 | ```bash 246 | $ gcloud pubsub topics list 247 | --- 248 | name: projects/pkslow/topics/pkslow-poc 249 | 250 | $ gcloud pubsub subscriptions list 251 | --- 252 | ackDeadlineSeconds: 20 253 | enableMessageOrdering: true 254 | expirationPolicy: 255 | ttl: 300000.500s 256 | labels: 257 | foo: bar 258 | messageRetentionDuration: 1200s 259 | name: projects/pkslow/subscriptions/pkslow-poc 260 | pushConfig: {} 261 | retainAckedMessages: true 262 | retryPolicy: 263 | maximumBackoff: 600s 264 | minimumBackoff: 10s 265 | topic: projects/pkslow/topics/pkslow-poc 266 | ``` 267 | 268 | 269 | 270 | 注意:我们并没有提供任何密码或密钥,那Terraform怎么可以直接操作我的GCP资源呢?因为它会根据环境变量**GOOGLE_APPLICATION_CREDENTIALS**来获取。 271 | 272 | 273 | 274 | ## 发送和接收消息 275 | 276 | 我们通过gcloud来发送消息到Pub/Sub上: 277 | 278 | ```bash 279 | $ gcloud pubsub topics publish pkslow-poc --message="www.pkslow.com" 280 | messageIds: 281 | - '3491736520339885' 282 | 283 | $ gcloud pubsub topics publish pkslow-poc --message="Larry Deng" 284 | messageIds: 285 | - '3491738650256958' 286 | 287 | $ gcloud pubsub topics publish pkslow-poc --message="Hi, pkslower" 288 | messageIds: 289 | - '3491739306095970' 290 | ``` 291 | 292 | 293 | 294 | 从Pub/Sub拉取消息: 295 | 296 | ```bash 297 | $ gcloud pubsub subscriptions pull pkslow-poc --auto-ack 298 | ``` 299 | 300 | 301 | 302 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/gcp/terraform-gcp-pubsub.pull.png) 303 | 304 | 305 | 306 | 我们还能在GCP界面上监控对应的队列,十分方便: 307 | 308 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/gcp/terraform-gcp-pubsub.console-pub.png) 309 | 310 | --- 311 | # 通过Google Cloud Storage(GCS)管理Terraform的状态State 312 | 管理Terraform状态文件的最佳方式是通过云端的统一的存储,如谷歌云就用GCS。 313 | 314 | 首先要创建一个Bucket: 315 | 316 | ```bash 317 | $ gsutil mb -p pkslow -l us-west1 -b on gs://pkslow-terraform 318 | Creating gs://pkslow-terraform/... 319 | 320 | $ gsutil ls gs:// 321 | gs://pkslow-terraform/ 322 | ``` 323 | 324 | 325 | 326 | 然后在Terraform文件中配置对应的信息: 327 | 328 | ```hcl 329 | terraform { 330 | backend "gcs" { 331 | bucket = "pkslow-terraform" 332 | prefix = "state/gcp/pubsub" 333 | } 334 | } 335 | ``` 336 | 337 | 338 | 339 | 初始化后,就会在Bucket上创建对应的目录: 340 | 341 | ```bash 342 | $ terraform init -plugin-dir=${TERRAFORM_PLUGIN} 343 | ``` 344 | 345 | 346 | 347 | 变更生效: 348 | 349 | ```bash 350 | $ terraform apply -auto-approve 351 | ``` 352 | 353 | 354 | 355 | 我们在浏览器查看一下,发现已经成功状态了对应的状态文件: 356 | 357 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/gcp/terraform-gcs.bucket-state.png) 358 | 359 | 360 | 361 | 通过远程的云端,不仅可以存入状态文件,也可以从状态文件读取数据,如一些输出变量。比如模块A创建了一个VM,而我们可能通过这种方式获取它的IP,以便在其它模块使用。大致的配置如下: 362 | 363 | ```hcl 364 | data "terraform_remote_state" "foo" { 365 | backend = "gcs" 366 | config = { 367 | bucket = "terraform-state" 368 | prefix = "prod" 369 | } 370 | } 371 | 372 | resource "template_file" "bar" { 373 | template = "${greeting}" 374 | 375 | vars { 376 | greeting = "${data.terraform_remote_state.foo.greeting}" 377 | } 378 | } 379 | ``` 380 | 381 | -------------------------------------------------------------------------------- /04.States状态管理.md: -------------------------------------------------------------------------------- 1 | > 《Terraform 101 从入门到实践》这本小册在[南瓜慢说官方网站](https://www.pkslow.com/tags/terraform101)和[GitHub](https://github.com/LarryDpk/terraform-101)两个地方同步更新,书中的示例代码也是放在GitHub上,方便大家参考查看。 2 | 3 | --- 4 | 5 | 6 | > 军书十二卷,卷卷有爷名。 7 | 8 | 9 | 10 | # 为什么需要状态管理 11 | 12 | Terraform的主要作用是管理云平台上的资源,通过声明式的HCL配置来映射资源,如果云平台上没有资源则需要创建,如果有则不用。那Terraform要实现这个功能有多种方式。 13 | 14 | 一种是每次执行apply命令时都调用API接口检查一下远程的云资源是否与配置文件一致,如果没有则创建,如果有但不同则需要修改,如果有且相同则不用变更。这种机制能保证云平台的资源与HCL配置是一致的。缺点也是非常明显的,每次都需要调用API去检查远程资源,效率很低,特别是当资源特别多的场景。 15 | 16 | 另一种方式是每次变更资源的时候,都会创建一个映射文件,它保存云平台资源的状态。这样每次执行`apply`命令时,只需要检查HCL配置与映射文件的差异即可。 17 | 18 | Terraform选择的是第二种方式,通过映射文件来保存资源状态,在Terraform的世界里叫状态文件。Terraform这样做是基于以下考虑: 19 | 20 | - 云平台真实状态的映射,解析状态文件即可以知道真实情况。 21 | - 元数据存储,如资源之间的依赖关系,需要通过依赖关系来知道创建或销毁顺序。 22 | - 提升性能,特别是在大规模云平台上,多次调用API去查询资源状态是很费时的。 23 | - 同步状态,通过远程状态文件来同步状态,这也是Terraform最佳的实践。 24 | 25 | 26 | 27 | 讲到这里,已经回答了之前在第一章留下的思考题: 28 | 29 | > 如果再次执行apply会不会再次创建一个文件呢?还是创建失败,因为文件已存在?为什么? 30 | 31 | 答案:不会创建,因为通过状态文件记录了变更,Terraform判断不再需要创建了。 32 | 33 | 34 | 35 | # 状态管理的示例 36 | 37 | 为了更多注意力放在状态管理上,我们还是使用最简单的例子`local_file`,具体代码如下: 38 | 39 | ```hcl 40 | resource "local_file" "terraform-introduction" { 41 | content = "https://www.pkslow.com" 42 | filename = "${path.root}/terraform-guides-by-pkslow.txt" 43 | } 44 | ``` 45 | 46 | 47 | 48 | 我们以实际操作及现象来讲解状态文件的作用和工作原理: 49 | 50 | | 操作 | 现象及说明 | 51 | | ----------------- | ------------------------------------------------------------ | 52 | | terraform apply | 生成资源:第一次生成 | 53 | | terraform apply | 没有变化:状态文件生成,不需要再创建 | 54 | | terraform destroy | 删除资源:根据状态文件的内容删除 | 55 | | terraform apply | 生成资源:状态显示没有资源,再次生成 | 56 | | 删除状态文件 | 没有变化 | 57 | | terraform apply | 生成资源:没有状态文件,直接生成资源和状态文件(插件做了容错处理,已存在也会新生成覆盖) | 58 | | 删除状态文件 | 没有变化 | 59 | | terraform destroy | 无法删除资源,没有资源存在的状态 | 60 | 61 | 62 | 63 | 我们一直在讲状态文件,我们先来看一下它的真面目。首先它的默认文件名是`terraform.tfstate`,默认会放在当前目录下。它是以`json`格式存储的信息,示例中的内容如下: 64 | 65 | ```json 66 | { 67 | "version": 4, 68 | "terraform_version": "1.0.11", 69 | "serial": 1, 70 | "lineage": "acb408bb-2a95-65fd-02e6-c23487f7a3f6", 71 | "outputs": {}, 72 | "resources": [ 73 | { 74 | "mode": "managed", 75 | "type": "local_file", 76 | "name": "test-file", 77 | "provider": "provider[\"registry.terraform.io/hashicorp/local\"]", 78 | "instances": [ 79 | { 80 | "schema_version": 0, 81 | "attributes": { 82 | "content": "https://www.pkslow.com", 83 | "content_base64": null, 84 | "directory_permission": "0777", 85 | "file_permission": "0777", 86 | "filename": "./terraform-guides-by-pkslow.txt", 87 | "id": "6db7ad1bbf57df0c859cd5fc62ff5408515b5fc1", 88 | "sensitive_content": null, 89 | "source": null 90 | }, 91 | "sensitive_attributes": [], 92 | "private": "bnVsbA==" 93 | } 94 | ] 95 | } 96 | ] 97 | } 98 | ``` 99 | 100 | 可以看到它记录了Terraform的版本信息,还有资源的详细信息:包括类型、名字、插件、属性等。有这些信息便可直接从状态文件里解析出具体的资源。 101 | 102 | 103 | 104 | # 状态管理命令 105 | 106 | 可以通过`terraform state`做一些状态管理: 107 | 108 | 显示状态列表: 109 | 110 | ```bash 111 | $ terraform state list 112 | local_file.test-file 113 | ``` 114 | 115 | 116 | 117 | 查看具体资源的状态信息: 118 | 119 | ```bash 120 | $ terraform state show local_file.test-file 121 | # local_file.test-file: 122 | resource "local_file" "test-file" { 123 | content = "https://www.pkslow.com" 124 | directory_permission = "0777" 125 | file_permission = "0777" 126 | filename = "./terraform-guides-by-pkslow.txt" 127 | id = "6db7ad1bbf57df0c859cd5fc62ff5408515b5fc1" 128 | } 129 | ``` 130 | 131 | 132 | 133 | 显示当前状态信息: 134 | 135 | ```bash 136 | $ terraform state pull 137 | ``` 138 | 139 | 140 | 141 | 重命名: 142 | 143 | ```bash 144 | $ terraform state mv local_file.test-file local_file.pkslow-file 145 | Move "local_file.test-file" to "local_file.pkslow-file" 146 | Successfully moved 1 object(s). 147 | 148 | $ terraform state list 149 | local_file.pkslow-file 150 | ``` 151 | 152 | 要注意这里只是修改状态文件的名字,代码里的HCL并不会修改。 153 | 154 | 155 | 156 | 删除状态里的资源: 157 | 158 | ```bash 159 | $ terraform state rm local_file.pkslow-file 160 | Removed local_file.pkslow-file 161 | Successfully removed 1 resource instance(s). 162 | ``` 163 | 164 | 165 | 166 | # 远程状态 167 | 168 | 状态文件默认是在本地目录上的`terraform.tfstate`文件,在团队使用中,每个人的电脑环境独立的,那么需要保证每个人当前的状态文件都是最新且与现实资源真实对应,简直是天方夜谭。而状态不一致所带的灾难也是极其可怕的。所以,状态文件最好是要存储在一个独立的大家可共同访问的位置。对于状态的管理的配置,Terraform称之为`Backends`。 169 | 170 | `Backend`是两种模式,分别是`local`和`remote`。`local`模式很好理解,就是使用本地路径来存储状态文件。配置示例如下: 171 | 172 | ```hcl 173 | terraform { 174 | backend "local" { 175 | path = "pkslow.tfstate" 176 | } 177 | } 178 | ``` 179 | 180 | 通过这样配置后,不再使用默认的`terraform.tfstate`文件,而是使用自定义的文件名`pkslow.tfstate`。 181 | 182 | 183 | 184 | 对于`remote`模式,则有多种配置方式,Terraform支持的有: 185 | 186 | - s3 187 | - gcs 188 | - oss 189 | - etcd 190 | - pg 191 | - http 192 | - kubernetes 193 | 194 | 等,能满足主流云平台的需求。每一个配置可以参考官网,在本地我采用数据库postgresql的方式,让大家都能快速实验。 195 | 196 | 我通过Docker的方式启动PostgreSQL,命令如下: 197 | 198 | ```bash 199 | $ docker run -itd \ 200 | --name terraform-postgres \ 201 | -e POSTGRES_DB=terraform \ 202 | -e POSTGRES_USER=pkslow \ 203 | -e POSTGRES_PASSWORD=pkslow \ 204 | -p 5432:5432 \ 205 | postgres:13 206 | ``` 207 | 208 | 209 | 210 | 在`terraform`块中配置`backend`,这里指定数据库连接信息即可,更多参数请参考:https://www.terraform.io/language/settings/backends/pg 211 | 212 | ```hcl 213 | terraform { 214 | backend "pg" { 215 | conn_str = "postgres://pkslow:pkslow@localhost:5432/terraform?sslmode=disable" 216 | } 217 | } 218 | ``` 219 | 220 | 221 | 222 | 当然,把敏感信息直接放在代码中并不合适,可以直接在命令行中传入参数: 223 | 224 | ```bash 225 | terraform init -backend-config="conn_str=postgres://pkslow:pkslow@localhost:5432/terraform?sslmode=disable" 226 | ``` 227 | 228 | 229 | 230 | 执行init和apply之后,连接数据库查看,会创建一个叫`terraform_remote_state`的Schema,在该Schema下有一张states表来存储对应的状态信息,如下: 231 | 232 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/04.states/state.postgresql-table.png) 233 | 234 | 表中字段name是namespace,而data是具体的状态信息,如下: 235 | 236 | ```json 237 | { 238 | "version": 4, 239 | "terraform_version": "1.0.11", 240 | "serial": 0, 241 | "lineage": "de390d13-d0e0-44dc-8738-d95b6d8f1868", 242 | "outputs": {}, 243 | "resources": [ 244 | { 245 | "mode": "managed", 246 | "type": "local_file", 247 | "name": "test-file", 248 | "provider": "provider[\"registry.terraform.io/hashicorp/local\"]", 249 | "instances": [ 250 | { 251 | "schema_version": 0, 252 | "attributes": { 253 | "content": "https://www.pkslow.com", 254 | "content_base64": null, 255 | "directory_permission": "0777", 256 | "file_permission": "0777", 257 | "filename": "./terraform-guides-by-pkslow.txt", 258 | "id": "6db7ad1bbf57df0c859cd5fc62ff5408515b5fc1", 259 | "sensitive_content": null, 260 | "source": null 261 | }, 262 | "sensitive_attributes": [], 263 | "private": "bnVsbA==" 264 | } 265 | ] 266 | } 267 | ] 268 | } 269 | ``` 270 | 271 | 272 | 273 | # Workspace 工作区 274 | 275 | 如果我们用Terraform代码生成了dev环境,但现在需要uat环境,该如何处理呢? 276 | 277 | 首先,不同环境的变量一般是不一样的,我们需要定义各种的变量文件如`dev.tfvars`、`uat.tfvars`和`prod.tfvars`等。但只有各自变量是不够的,因为还有状态。状态也必须要隔离,而`Workspace`就是Terraform用来隔离状态的方式。默认的工作区为`default`,如果没有指定,则表示工作于`default`工作区中。而当指定了工作区,状态文件就会与工作区绑定。 278 | 279 | 创建一个工作区并切换: 280 | 281 | ```bash 282 | $ terraform workspace new pkslow 283 | ``` 284 | 285 | 286 | 287 | 切换到已存在的工作区: 288 | 289 | ```bash 290 | $ terraform workspace select pkslow 291 | ``` 292 | 293 | 而当我们处于某个工作区时,是可以获取工作区的名字的,引用为:`${terraform.workspace}`,示例如下: 294 | 295 | ```hcl 296 | resource "aws_instance" "example" { 297 | count = "${terraform.workspace == "default" ? 5 : 1}" 298 | 299 | # ... other arguments 300 | } 301 | ``` 302 | 303 | 304 | 305 | 之前讲过默认的状态文件名为`terraform.tfstate`;而在多工作区的情况下(只要你创建了一个非默认工作区),状态文件就会存在`terraform.tfstate.d`目录下。而在远程状态的情况下,也会有一个映射,Key为工作区名,Value一般是状态内容。 306 | 307 | 308 | 309 | # 敏感数据 310 | 311 | 本地状态文件都是明文存储状态信息的,所以要保护好自己的状态文件。对于远程状态文件,有些存储方案是支持加密的,会对敏感数据(`sensitive`)进行加密。 312 | 313 | 314 | 315 | # 状态锁 316 | 317 | 本地状态文件下不需要状态锁,因为只有一个人在变更。而远程状态的情况下,就可能出现竞争了。比如一个人在apply,而另一个人在destroy,那就乱了。而状态锁可以确保远程状态文件只能被一个人使用。但不是所有远程状态的方式都支持锁的,一般常用的都会支持,如GCS、S3等。 318 | 319 | 320 | 321 | 所以,每当我们在执行变更时,Terraform总会先尝试去拿锁,如果拿锁失败,就该命令失败。可以强制解锁,但要非常小心,一般只建议在自己明确知道安全的时候才使用,比如死锁了。 322 | 323 | 324 | 325 | # 共享状态-数据源 326 | 327 | 既然远程状态文件是可以共享的,那状态信息也是可以共享的。这样会带来的一个好处是,即使两个根模块,也是可以共享信息的。比如我们在根模块A创建了一个数据库,而根模块B需要用到数据库的信息如IP,这样通过远程状态文件就可以共享给根模块B了。 328 | 329 | > 注意这里我强调的是根模块,因为如果A和B在同一个根模块下,那就不需要通过远程状态的方式来共享状态了。 330 | 331 | 332 | 333 | 远程状态的示例: 334 | 335 | ```hcl 336 | data "terraform_remote_state" "vpc" { 337 | backend = "remote" 338 | 339 | config = { 340 | organization = "hashicorp" 341 | workspaces = { 342 | name = "vpc-prod" 343 | } 344 | } 345 | } 346 | 347 | resource "aws_instance" "foo" { 348 | # ... 349 | subnet_id = data.terraform_remote_state.vpc.outputs.subnet_id 350 | } 351 | ``` 352 | 353 | 354 | 355 | 本地状态的示例: 356 | 357 | ```hcl 358 | data "terraform_remote_state" "vpc" { 359 | backend = "local" 360 | 361 | config = { 362 | path = "..." 363 | } 364 | } 365 | 366 | resource "aws_instance" "foo" { 367 | # ... 368 | subnet_id = data.terraform_remote_state.vpc.outputs.subnet_id 369 | } 370 | ``` 371 | 372 | 373 | 374 | 要注意的是,只有根模块的输出变量才能被共享,子模块是不能被获取的。 375 | -------------------------------------------------------------------------------- /05.HCL语法.md: -------------------------------------------------------------------------------- 1 | > 《Terraform 101 从入门到实践》这本小册在[南瓜慢说官方网站](https://www.pkslow.com/tags/terraform101)和[GitHub](https://github.com/LarryDpk/terraform-101)两个地方同步更新,书中的示例代码也是放在GitHub上,方便大家参考查看。 2 | 3 | --- 4 | 5 | 介绍了Terraform一些比较基础的概念后,我们可以先了解一下Terraform的语法,也就是HCL的语法。 6 | 7 | 8 | 9 | # 变量Variables 10 | 11 | 变量是实现代码复用的一种方式,同样的代码不同的变量往往会有不同的效果。而在Terraform里,有一个概念非常重要,就是变量都是从属于模块的。变量无法跨模块引用。即在模块A定义的变量X,无法在模块B中直接引用。但父模块的变量,可以作为子模块的入参;而子模块的输出变量可以被父模块获取。 12 | 13 | ## 变量类型 14 | 15 | ### 从语言角度 16 | 17 | 跟任何编程语言一样,变量都是有类型的,Terraform的变量类型从语言的角度可分为两大类:基本类型和组合类型,具体如下: 18 | 19 | 基本类型: 20 | 21 | - 字符串string,如`"pkslow.com"` 22 | - 数字number,如`319`或`5.11` 23 | - 布尔值bool,如`true` 24 | 25 | 26 | 27 | 组合类型: 28 | 29 | - 列表list(),如`["dev", "uat", "prod"]` 30 | - 集合set(),如`set(...)` 31 | - 映射map(),如`{name="Larry", age="18"}` 32 | - 对象object({name1=T1, name2=T2}) 33 | - 元组tuple([T1,T2,T3...]) 34 | 35 | 36 | 37 | 如果不想指定某个类型,可以用`any`来表示任意类型;或者不指定,默认为任意类型。 38 | 39 | 40 | 41 | ### 从功能角度 42 | 43 | 从功能角度来看,变量可以分为输入变量、输出变量和本地变量。 44 | 45 | 输入变量是模块接收外部变量的方式,它定义在`variable`块中,如下: 46 | 47 | ```hcl 48 | variable "image_id" { 49 | type = string 50 | } 51 | 52 | variable "availability_zone_names" { 53 | type = list(string) 54 | default = ["us-west-1a"] 55 | } 56 | 57 | variable "docker_ports" { 58 | type = list(object({ 59 | internal = number 60 | external = number 61 | protocol = string 62 | })) 63 | default = [ 64 | { 65 | internal = 8300 66 | external = 8300 67 | protocol = "tcp" 68 | } 69 | ] 70 | } 71 | ``` 72 | 73 | 74 | 75 | 输出变量定义了一个模块对外返回的变量,通过`output`块来定义,如下: 76 | 77 | ```hcl 78 | output "instance_ip_addr" { 79 | value = aws_instance.server.private_ip 80 | } 81 | ``` 82 | 83 | 84 | 85 | 本地变量是模块内定义且可引用的临时变量,在`locals`块中定义,如下: 86 | 87 | ```hcl 88 | locals { 89 | service_name = "forum" 90 | owner = "Community Team" 91 | } 92 | ``` 93 | 94 | 95 | 96 | # 输入变量Input Variable 97 | 98 | 输入变量是定义在`variable`块中的,它就像是函数的入参。 99 | 100 | ## 定义输入变量 101 | 102 | 定义`variable`有很多可选属性: 103 | 104 | - 类型type:指定变量是什么类型;如果没有指定,则可以是任意类型; 105 | - 默认值default:变量的默认值,定义后可以不用提供变量的值,注意它的值的类型要与type对应上; 106 | - 说明description:说明这个变量的作用和用途; 107 | - 校验validation:提供校验逻辑来判断输入的变量是否合法; 108 | - 敏感性sensitive:定义变量是否敏感,如果是则不会显示;默认为`false`; 109 | - 可空nullable:如果为true则可以为空,否则不能。默认为`true`。 110 | 111 | 112 | 113 | 所有属性都显性指定如下面例子所示: 114 | 115 | ```hcl 116 | variable "env" { 117 | type = string 118 | default = "dev" 119 | description = "environment name" 120 | sensitive = false 121 | nullable = false 122 | validation { 123 | condition = contains(["dev", "uat", "prod"], var.env) 124 | error_message = "The env must be one of dev/uat/prod." 125 | } 126 | } 127 | ``` 128 | 129 | 这个变量名为`env`,表示环境名,默认值为`dev`,这个值必须为`dev`、`uat`和`prod`中的其中一个。如果输出一个非法的值,会报错: 130 | 131 | ```bash 132 | $ terraform plan -var="env=sit" 133 | ╷ 134 | │ Error: Invalid value for variable 135 | │ 136 | │ on input.tf line 1: 137 | │ 1: variable "env" { 138 | │ 139 | │ The env must be one of dev/uat/prod. 140 | ``` 141 | 142 | 143 | 144 | ## 使用输入变量 145 | 146 | 只有定义了变量才可以使用,使用的方式是`var.name`。比如这里定义了两个变量`env`和`random_string_length`: 147 | 148 | ```hcl 149 | variable "env" { 150 | type = string 151 | default = "dev" 152 | } 153 | 154 | variable "random_string_length" { 155 | type = number 156 | default = 10 157 | } 158 | ``` 159 | 160 | 161 | 162 | 则使用如下: 163 | 164 | ```hcl 165 | resource "random_string" "random" { 166 | length = var.random_string_length 167 | lower = true 168 | special = false 169 | } 170 | 171 | locals { 172 | instance_name = "${var.env}-${random_string.random.result}" 173 | } 174 | 175 | output "instance_name" { 176 | value = local.instance_name 177 | } 178 | ``` 179 | 180 | 181 | 182 | ## 传入变量到根模块 183 | 184 | 要从外部传入变量到根模块,有多种方式,常见的有以下几种,按优先级从低到高: 185 | 186 | - 环境变量`export TF_VAR_image_id=ami-abc123` 187 | - `terraform.tfvars`文件; 188 | - `terraform.tfvars.json`文件; 189 | - `*.auto.tfvars`或`*.auto.tfvars.json`文件; 190 | 191 | - 命令行参数`-var`传入一个变量;命令行参数`-var-file`传入一个变量的集合文件; 192 | 193 | 194 | 195 | 在实践中,最常用的还是通过命令行来传入参数,因为一般需要指定不同环境的特定变量,所以会把变量放到文件中,然后通过命令行指定特定环境的主文件: 196 | 197 | ```bash 198 | $ terraform apply -var="env=uat" 199 | $ terraform apply -var-file="prod.tfvars" 200 | ``` 201 | 202 | 203 | 204 | 而`prod.tfvars`的内容如下: 205 | 206 | ```hcl 207 | env = "prod" 208 | random_string_length = 12 209 | ``` 210 | 211 | 我们可以定义`dev.tfvars`、`uat.tfvars`和`prod.tfvars`等,要使用不同环境的变量就直接改变文件名即可。 212 | 213 | 214 | 215 | # 输出变量Output Variable 216 | 217 | 有输入就有输出,输出变量就像是模块的返回值,比如我们调用一个模块去创建一台服务,那就要获取服务的IP,这个IP事先是不知道,它是服务器创建完后的结果之一。输出变量有以下作用: 218 | 219 | - 子模块的输出变量可以暴露一些资源的属性; 220 | - 根模块的输出变量可以在apply后输出到控制台; 221 | - 根模块的输出变量可以通过`remote state`的方式共享给其它Terraform配置,作为数据源。 222 | 223 | 224 | 225 | ## 定义输出变量 226 | 227 | 输出变量需要定义在`output`块中,如下: 228 | 229 | ```hcl 230 | output "instance_ip_addr" { 231 | value = aws_instance.server.private_ip 232 | } 233 | ``` 234 | 235 | 这个`value`可以是reource的属性,也可以是各种变量计算后的结果。只要在执行apply的时候才会去计算输出变量,像plan是不会执行计算的。 236 | 237 | 238 | 239 | 还可以定义输出变量的一些属性: 240 | 241 | - `description`:输出变量的描述,说明清楚这个变量是干嘛的; 242 | - `sensitive`:如果是`true`,就不会在控制台打印出来; 243 | - `depends_on`:显性地定义依赖关系。 244 | 245 | 246 | 247 | 完整的定义如下: 248 | 249 | ```hcl 250 | output "instance_ip_addr" { 251 | value = aws_instance.server.private_ip 252 | description = "The private IP address of the main server instance." 253 | sensitive = false 254 | depends_on = [ 255 | # Security group rule must be created before this IP address could 256 | # actually be used, otherwise the services will be unreachable. 257 | aws_security_group_rule.local_access, 258 | ] 259 | } 260 | ``` 261 | 262 | 263 | 264 | 265 | 266 | ## 引用输出变量 267 | 268 | 引用输出变量很容易,表达式为`module..`,如果前面的输出变量定义在模块`pkslow_server`中,则引用为:`module.pkslow_server.instance_ip_addr`。 269 | 270 | 271 | 272 | # 本地变量Local Variable 273 | 274 | 本地变量有点类似于其它语言代码中的局部变量,在Terraform模块中,它的一个重要作用是避免重复计算一个值。 275 | 276 | ```hcl 277 | locals { 278 | instance_name = "${var.env}-${random_string.random.result}-${var.suffix}" 279 | } 280 | ``` 281 | 282 | 这里定义了一个本地变量`instance_name`,它的值是一个复杂的表达式。这时我们可以通过`local.xxx`的形式引用,而不用再写复杂的表达式了。如下: 283 | 284 | ```hcl 285 | output "instance_name" { 286 | value = local.instance_name 287 | } 288 | ``` 289 | 290 | 这里要特别注意:定义本地变量的关键字是`locals`块,里面可以有多个变量;而引用的关键字是`local`,并没有`s`。 291 | 292 | 293 | 294 | 一般我们是建议需要重复引用的复杂的表达式才使用本地变量,不然太多本地变量就会影响可读性。 295 | 296 | 297 | 298 | 299 | 300 | # 对变量的引用 301 | 302 | 定义了变量就需要对其进行引用,前面的讲解其实已经讲过了部分变量的引用,这些把所有列出来。 303 | 304 | 305 | 306 | | 类型 | 引用方式 | 307 | | ----------------------- | ------------------------------------------------------------ | 308 | | 资源Resources | `.` | 309 | | 输入变量Input Variables | `var.` | 310 | | 本地变量Local Values | `local.` | 311 | | 子模块的输出 | `module..` | 312 | | 数据源Data Sources | `data..` | 313 | | 路径和Terraform相关 | `path.module`:模块所在路径
`path.root`:根模块的路径
`path.cwd`:一般与根模块相同,其它高级用法除外
`terraform.workspace`:工作区名字 | 314 | | 块中的本地变量 | `count.index`:count循环的下标;
`each.key`/`each.value`:for each循环的键值;
`self`:在provisioner的引用; | 315 | 316 | 上面都是单值的引用,如果是List或Map这种复杂类型,就要使用中括号`[]`来引用。 317 | 318 | `aws_instance.example[0].id`:引用其中一个元素; 319 | 320 | `aws_instance.example[*].id`:引用列表的所有id值; 321 | 322 | `aws_instance.example["a"].id`:引用key为`a`的元素; 323 | 324 | `[for value in aws_instance.example: value.id]`:返回所有id为列表; 325 | 326 | 327 | 328 | # 运算符 329 | 330 | 与其它语言一样,Terraform也有运算符可以用,主要是用于数值计算和逻辑计算。以下运算符按优先级从高到低如下: 331 | 332 | 1. `!`取反,`-`取负 333 | 2. `*`乘号,`/`除号,`%`取余 334 | 3. `+`加号,`-`减号 335 | 4. `>`,`>=`,`<`,`<=`:比较符号 336 | 5. `==`等于,`!=`不等于 337 | 6. `&&`与门 338 | 7. `||`或门 339 | 340 | 341 | 342 | 当然,用小括号可以改变这些优秀级,如`(1 + 2) * 3`。 343 | 344 | 345 | 346 | 注意:对于结构化的数据比较需要注意类型是否一致。比如`var.list == []`按理说应该返回`true`,而`list`为空时。当`[]`实际表示是元组`tuple([])`,所以它们不匹配。可以使用`length(var.list) == 0`的方式。 347 | 348 | 349 | 350 | # 条件表达式 351 | 352 | 条件表达式的作用是在两个值之间选一个,条件为真则选第一个,条件为假则选第二个。形式如下: 353 | 354 | ```hcl 355 | condition ? true_value : false_value 356 | ``` 357 | 358 | 示例如下: 359 | 360 | ```hcl 361 | env = var.env !="" ? var.env : "dev" 362 | ``` 363 | 364 | 意思是给`env`赋值,如果`var.env`不为空就把输入变量`var.env`的值赋给它,如果为空则赋默认值`dev`。 365 | 366 | 367 | 368 | # for表达式 369 | 370 | 使用`for`表达式可以创建一些复杂的值,而且可以使用一些转换和计算对值计算再返回。如将字符串列表转化成大写: 371 | 372 | ```hcl 373 | > [for s in ["larry", "Nanhua", "Deng"] : upper(s)] 374 | [ 375 | "LARRY", 376 | "NANHUA", 377 | "DENG", 378 | ] 379 | ``` 380 | 381 | 可以获取下标和值: 382 | 383 | ```hcl 384 | > [for i,v in ["larry", "Nanhua", "Deng"] : "${i}.${v}"] 385 | [ 386 | "0.larry", 387 | "1.Nanhua", 388 | "2.Deng", 389 | ] 390 | ``` 391 | 392 | 393 | 394 | 对于Map的for表达式: 395 | 396 | ```hcl 397 | > [for k,v in {name: "Larry Deng", age: 18, webSite: "www.pkslow.com"} : "${k}: ${v}"] 398 | [ 399 | "age: 18", 400 | "name: Larry Deng", 401 | "webSite: www.pkslow.com", 402 | ] 403 | ``` 404 | 405 | 406 | 407 | 通过条件过滤数据: 408 | 409 | ```hcl 410 | > [for i in range(1, 10) : i*3 if i%2==0] 411 | [ 412 | 6, 413 | 12, 414 | 18, 415 | 24, 416 | ] 417 | ``` 418 | 419 | 420 | 421 | # 动态块Dynamic Block 422 | 423 | 动态块的作用是根据变量重复某一块配置。这在Terraform是会遇见的。 424 | 425 | ```hcl 426 | resource "aws_elastic_beanstalk_environment" "tfenvtest" { 427 | name = "tf-test-name" 428 | application = "${aws_elastic_beanstalk_application.tftest.name}" 429 | solution_stack_name = "64bit Amazon Linux 2018.03 v2.11.4 running Go 1.12.6" 430 | 431 | dynamic "setting" { 432 | for_each = var.settings 433 | content { 434 | namespace = setting.value["namespace"] 435 | name = setting.value["name"] 436 | value = setting.value["value"] 437 | } 438 | } 439 | } 440 | ``` 441 | 442 | 比如这里的例子,就会重复`setting`块。重复的次数取决于`for_each`后面跟的变量。 443 | 444 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /01.Terraform初相识.md: -------------------------------------------------------------------------------- 1 | > 《Terraform 101 从入门到实践》这本小册在[南瓜慢说官方网站](https://www.pkslow.com/tags/terraform101)和[GitHub](https://github.com/LarryDpk/terraform-101)两个地方同步更新,书中的示例代码也是放在GitHub上,方便大家参考查看。 2 | 3 | --- 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | > 初闻不知Terraform,再闻已是云中人。 13 | 14 | 15 | 16 | # 什么叫基础设施即代码? 17 | 18 | 在以前,当我们需要把应用部署在服务器时,需要购买多台服务器和机房、组装交换机和网络、不间断电源UPS等。随着云时代的到来,我们可以在IaaS(Infrastructure as a Service)平台直接购买所有的基础设施,包括服务器、专用网络、DNS、负载均衡等,而你只需要专注于应用层面即可。 19 | 20 | IaaS(Infrastructure as a Service)的意思是基础设施即服务,它是云服务的基础。著名的IaaS厂商有亚马逊、微软、谷歌和阿里云等。 21 | 22 | 23 | 24 | 云厂商为我们解决了许多运维问题:我们不再需要自己管理物理机器,而且能够根据需要随时创建和销毁云机器,还能根据业务和性能要求指定创建服务器的配置和数量。这种便利对于创业型的小公司和个人开发者尤其重要。 25 | 26 | 27 | 28 | 随时公司业务的良好发展,所需要的硬件资源越来越多,架构越来越复杂。通过界面操作手工创建服务器、数据库等资源的方式带来越来越多的问题。首先,只要是人工操作,都会有失误的可能,没有人能保证自己不会犯错;而人工操作在软件行业发生事故的案例屡见不鲜。其次,为保证正确率,人工操作一般只能串行,资源多的时候时间会很长。最后,如果我需要根据开发环境的配置再创建一个测试环境和生产环境,人工操作可能会造成差异和错误。 29 | 30 | 因此,对于这种复杂需求,最佳的方式是通过代码来创建所有硬件资源。这种思想就是基础设施即代码(Infrastructure as Code,很简称IaC),通过代码与定义、部署、更新和销毁基础设施。把硬件映射为软件,而开发和运维人员通过管理代码来管理硬件。 31 | 32 | IaC的好处有: 33 | 34 | - 自动化:用软件代替人工,实现自动化,减少风险和安全问题; 35 | - 效率高:软件可以并行创建资源,大大提高效率; 36 | - 记录与追踪:通过代码与执行情况,记录硬件变更,出问题也可以追溯; 37 | - 重用与复制:抽取公共模块实现重用,如创建一个Kubernetes集群的资源可以封装成一个模块。 38 | 39 | 40 | 41 | 最终,实现快速安全地应用部署交付(Delivery)。 42 | 43 | 44 | 45 | # IaC工具 46 | 47 | 在IaC这方面的优秀工具还是非常多的,而且不同的工具完成不同的职责,下面列出一些比较常见的工具: 48 | 49 | | 图标 | 工具名 | GitHub STAR数 | 50 | | ------------------------------------------------------------ | --------------------------------------------------- |--------------| 51 | | | [Ansible](https://github.com/ansible/ansible) | 57.7k | 52 | | | [Terraform](https://github.com/hashicorp/terraform) | 37.6k | 53 | | | [Vagrant](https://github.com/hashicorp/vagrant) | 23k | 54 | | | [Chef](https://github.com/chef/chef) | 6.8k | 55 | | | [Puppet](https://github.com/puppetlabs/puppet) | 6.4k | 56 | | | AWS CloudFormation | | 57 | | | Azure Resource Manager | | 58 | | | Google Cloud Deployment Manager | | 59 | 60 | 61 | 62 | 其中,Ansible在配置自动化应该是领头羊的地位。而Terraform则是在服务开通上的事实标准。这里并不想给各个工具做具体介绍,感兴趣的可以去官网或GitHub了解。 63 | 64 | 65 | 66 | 注:有些文章或书籍会把Docker和Kubernetes也列为IaC工具,它们的主要职责是在容器与服务编排方面。 67 | 68 | 69 | 70 | 71 | 72 | # Terraform隆重登场 73 | 74 | ## Terraform是什么 75 | 76 | 我们的主角Terraform终于登场了。它是由HashiCorp公司研发的开源的IaC工具,它是由GO语言编写的,可以在各个平台上运行,支持Linux、Mac、Windows等。它简单易用,即使没有太多代码经验的人,也能读懂Terraform的配置代码HCL。 77 | 78 | > HCL,即HashiCorp Configuration Language,是HashiCorp公司开发的配置语言。后续我们会介绍一些常用语法。 79 | 80 | Terraform是一个安全高效的用于对基础设施进行创建和变更且进行版本控制的工具。它支持私有云和公有云,如AWS、Azure、GCP和阿里云等。它的官方网站为[https://www.terraform.io](https://www.terraform.io/)。 81 | 82 | 83 | 84 | ### 特性 85 | 86 | 主要特性有: 87 | 88 | - 基础设施即代码:通过配置语言HCL来描述基础设施,也让代码更好地共享和重用。 89 | - 变更计划:在实际变更前可以根据代码和状态生成即将要发生变更的计划,它能告诉你将要生成、改变和销毁哪些资源。这可以在执行变更前再做最后的检查,为基础设施提供多一层保护。 90 | - 资源视图:可以根据依赖关系创建出资源视图,可以直观地查看整个基础设施的关系。 91 | - 自动化:无须人工干预就可以完成变更。 92 | 93 | 94 | 95 | ### 版本号 96 | 97 | 截至2021年12月02日,Terraform的最新版本为1.0.11,而它在2021年6月8日才正式发布1.0.0版本。可见Terraform是如此年轻且有活力。而在Terraform还不是1.0.0版本的时候,已经有大量公司在生产环境上使用了。 98 | 99 | 100 | 101 | ## 架构与原理 102 | 103 | Terraform是一个由Go语言编写的程序,它会读取HCL语言编写的配置文件,然后将变更信息通过RPC与插件通信,由插件调用云厂商的API完成变更操作。这就是Terraform的工作原理,架构图如下: 104 | 105 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/01.introduction/arch.how-it-works.png) 106 | 107 | 108 | 109 | ## 基本概念 110 | 111 | **Terraform core**:Terraform的核心组件,类似于指挥官,负责解析配置、管理状态、模块等核心功能。 112 | 113 | **插件Plugin**:完成具体变更的组件,因为Terraform支持多种平台,它并没有把对所有平台的支持都放到核心组件中实现,而是通过插件的方式来提供这些功能。需要对接什么平台,就加入什么平台的插件,非常方面。 114 | 115 | **模块module**:可以将完成特定功能的HCL封装成一个模块,以实现代码复用。类似于其它编程语言中的函数或方法。有入参和出参,一切都可自定义。 116 | 117 | **状态state**:状态存在专门的状态文件里,它是作用是记录实际基础设施的状态。当再次执行变更请求时,Terraform会读取状态文件,判断是否真的需要变更实际的基础设施。如果状态文件记录的状态与HCL描述的一致,就不用再执行变更操作了。 118 | 119 | 120 | 121 | ## 初体验 122 | 123 | ### 下载安装 124 | 125 | Terraform就是一个二进制的程序,只要下载并添加到PATH中去就可以了。各个系统的安装方式没有太大差异。这里以Mac系统为例,做个简单介绍。 126 | 127 | 下载程序: 128 | 129 | 可以直接到官网界面(https://www.terraform.io/downloads.html)去下载,请根据自己的系统选择对应的文件: 130 | 131 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/01.introduction/introduction.download.png) 132 | 133 | 134 | 135 | 下载后进行解压,并将该程序添加到环境变量中。 136 | 137 | 比如我的Terraform放在路径/Users/larry/Software/terraform中,则添加到环境变量的命令如下: 138 | 139 | ```bash 140 | export PATH=$PATH:/Users/larry/Software/terraform 141 | ``` 142 | 143 | 为了让它一直生效,我把上面命令放在home目录下的.bash_profile文件中。 144 | 145 | 检查是否安装成功如下: 146 | 147 | ```bash 148 | $ terraform version 149 | Terraform v1.0.11 150 | on darwin_amd64 151 | ``` 152 | 153 | 154 | 155 | 如果在纯终端的环境下,也可以通过命令进行下载和解压,命令如下: 156 | 157 | ```bash 158 | # 下载安装包 159 | $ wget https://releases.hashicorp.com/terraform/1.0.11/terraform_1.0.11_darwin_amd64.zip 160 | # 解压 161 | $ unzip terraform_1.0.11_darwin_amd64.zip 162 | ``` 163 | 164 | 165 | 166 | ### 最简单的任务:创建一个文件 167 | 168 | Terraform的主要应用场景是云服务的基础设施管理,但为了让大家能快速的接触与体验Terraform,我会先选择最简单的一个插件来入门,以免需要太多的环境设置。我们的任务是创建一个文本文件,内容由我们来指定。可以通过插件hashicorp/local来完成。 169 | 170 | 171 | 172 | 在当前目录创建一个main.tf文件,完整的代码如下: 173 | 174 | ```hcl 175 | terraform { 176 | required_version = "= v1.0.11" 177 | 178 | required_providers { 179 | local = { 180 | source = "hashicorp/local" 181 | version = "= 2.1.0" 182 | } 183 | } 184 | } 185 | 186 | resource "local_file" "terraform-introduction" { 187 | content = "Hi guys, this is the tutorial of Terraform from pkslow.com" 188 | filename = "${path.module}/terraform-introduction-by-pkslow.txt" 189 | } 190 | ``` 191 | 192 | 193 | 194 | 然后执行下面命令: 195 | 196 | ```bash 197 | $ terraform init 198 | 199 | Initializing the backend... 200 | 201 | Initializing provider plugins... 202 | - Finding hashicorp/local versions matching "2.1.0"... 203 | - Installing hashicorp/local v2.1.0... 204 | - Installed hashicorp/local v2.1.0 (signed by HashiCorp) 205 | 206 | Terraform has created a lock file .terraform.lock.hcl to record the provider 207 | selections it made above. Include this file in your version control repository 208 | so that Terraform can guarantee to make the same selections by default when 209 | you run "terraform init" in the future. 210 | 211 | Terraform has been successfully initialized! 212 | 213 | You may now begin working with Terraform. Try running "terraform plan" to see 214 | any changes that are required for your infrastructure. All Terraform commands 215 | should now work. 216 | 217 | If you ever set or change modules or backend configuration for Terraform, 218 | rerun this command to reinitialize your working directory. If you forget, other 219 | commands will detect it and remind you to do so if necessary. 220 | ``` 221 | 222 | 223 | 224 | 看命令的输出结果可以知道,Terraform会自动帮我们去下载对应版本的插件hashicorp/local,并做一些初始化的操作。 225 | 226 | 227 | 228 | 接着我们通过命令`terraform plan`来查看将要执行的变更计划: 229 | 230 | ```bash 231 | $ terraform plan 232 | 233 | Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: 234 | + create 235 | 236 | Terraform will perform the following actions: 237 | 238 | # local_file.terraform-introduction will be created 239 | + resource "local_file" "terraform-introduction" { 240 | + content = "Hi guys, this is the tutorial of Terraform from pkslow.com" 241 | + directory_permission = "0777" 242 | + file_permission = "0777" 243 | + filename = "./terraform-introduction-by-pkslow.txt" 244 | + id = (known after apply) 245 | } 246 | 247 | Plan: 1 to add, 0 to change, 0 to destroy. 248 | ``` 249 | 250 | 251 | 252 | 输出日志中会提示需要创建、改变和销毁多少资源。 253 | 254 | ```bash 255 | Plan: 1 to add, 0 to change, 0 to destroy 256 | ``` 257 | 258 | 这里表示会创建一个资源。 259 | 260 | 261 | 262 | 263 | 264 | 废话少说,我们直接执行变更: 265 | 266 | ```bash 267 | $ terraform apply 268 | 269 | Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: 270 | + create 271 | 272 | Terraform will perform the following actions: 273 | 274 | # local_file.terraform-introduction will be created 275 | + resource "local_file" "terraform-introduction" { 276 | + content = "Hi guys, this is the tutorial of Terraform from pkslow.com" 277 | + directory_permission = "0777" 278 | + file_permission = "0777" 279 | + filename = "./terraform-introduction-by-pkslow.txt" 280 | + id = (known after apply) 281 | } 282 | 283 | Plan: 1 to add, 0 to change, 0 to destroy. 284 | 285 | Do you want to perform these actions? 286 | Terraform will perform the actions described above. 287 | Only 'yes' will be accepted to approve. 288 | 289 | Enter a value: 290 | ``` 291 | 292 | 293 | 294 | 会让你确认是否执行变更,如果是,则输入yes。我们直接输入yes并按回车。 295 | 296 | ```bash 297 | Enter a value: yes 298 | 299 | local_file.terraform-introduction: Creating... 300 | local_file.terraform-introduction: Creation complete after 0s [id=f63c7933c953ea2d03820d1ec35a80c718bd4777] 301 | 302 | Apply complete! Resources: 1 added, 0 changed, 0 destroyed. 303 | ``` 304 | 305 | 306 | 307 | 成功执行,创建了文件。 308 | 309 | ```bash 310 | $ ls -l 311 | total 24 312 | -rw-r--r-- 1 larry staff 344 Dec 3 00:01 main.tf 313 | -rwxr-xr-x 1 larry staff 55 Dec 3 00:13 terraform-introduction-by-pkslow.txt 314 | -rw-r--r-- 1 larry staff 921 Dec 3 00:13 terraform.tfstate 315 | ``` 316 | 317 | 上面还有一个tfstate文件,是用来记录状态的,以后会详细讲这块内容。 318 | 319 | 320 | 321 | 查看一下文件内容: 322 | 323 | ```bash 324 | $ cat terraform-introduction-by-pkslow.txt 325 | Hi guys, this is the tutorial of Terraform from pkslow.com 326 | ``` 327 | 328 | 与我们预期的内容一致。 329 | 330 | 331 | 332 | 如果再次执行apply会不会再次创建一个文件呢?还是创建失败,因为文件已存在? 333 | 334 | 带着这样的问题,我们再执行一次: 335 | 336 | ```bash 337 | $ terraform apply 338 | local_file.terraform-introduction: Refreshing state... [id=f63c7933c953ea2d03820d1ec35a80c718bd4777] 339 | 340 | No changes. Your infrastructure matches the configuration. 341 | 342 | Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed. 343 | 344 | Apply complete! Resources: 0 added, 0 changed, 0 destroyed. 345 | ``` 346 | 347 | 发现提示不需要变更,不会执行任何操作。 348 | 349 | 350 | 351 | 大家可以思考一下为什么,答案会在后面章节揭晓。 352 | 353 | 354 | 355 | 现在我不需要这个文件呢,通过destroy命令可以删除: 356 | 357 | ```bash 358 | $ terraform destroy 359 | local_file.terraform-introduction: Refreshing state... [id=f63c7933c953ea2d03820d1ec35a80c718bd4777] 360 | 361 | Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: 362 | - destroy 363 | 364 | Terraform will perform the following actions: 365 | 366 | # local_file.terraform-introduction will be destroyed 367 | - resource "local_file" "terraform-introduction" { 368 | - content = "Hi guys, this is the tutorial of Terraform from pkslow.com" -> null 369 | - directory_permission = "0777" -> null 370 | - file_permission = "0777" -> null 371 | - filename = "./terraform-introduction-by-pkslow.txt" -> null 372 | - id = "f63c7933c953ea2d03820d1ec35a80c718bd4777" -> null 373 | } 374 | 375 | Plan: 0 to add, 0 to change, 1 to destroy. 376 | 377 | Do you really want to destroy all resources? 378 | Terraform will destroy all your managed infrastructure, as shown above. 379 | There is no undo. Only 'yes' will be accepted to confirm. 380 | 381 | Enter a value: yes 382 | 383 | local_file.terraform-introduction: Destroying... [id=f63c7933c953ea2d03820d1ec35a80c718bd4777] 384 | local_file.terraform-introduction: Destruction complete after 0s 385 | 386 | Destroy complete! Resources: 1 destroyed. 387 | ``` 388 | 389 | 390 | 391 | 一样需要你确认是否真的需要删除,输入yes回车即可。 392 | 393 | 394 | 395 | 到这里,就已经真正地带大家体验了一下Terraform是如何工作的,介绍了它的整个流程,也就是Terraform官网所说的**Write, Plan, Apply**。希望大家能真正动手实践,包括后续的实验,这跟学编程语言是一样的。 396 | 397 | 398 | 399 | 400 | 401 | 最后,对于本次实验我想提几点: 402 | 403 | - 其中的plan命令不是必须的,它是展示即将发生的变更,你可以直接apply也是可以的; 404 | - 可以通过plan命令输出计划文件,然后apply的时候指定计划文件; 405 | - 命令apply和destroy可以不必交互式输入yes,通过添加参数`-auto-approve`即可。 406 | 407 | 408 | 409 | --- 410 | 411 | 参考:https://www.thorntech.com/15-infrastructure-as-code-tools/ 412 | 413 | 插件:https://registry.terraform.io/providers/hashicorp/local/latest 414 | 415 | -------------------------------------------------------------------------------- /Functions函数.md: -------------------------------------------------------------------------------- 1 | > 《Terraform 101 从入门到实践》这本小册在[南瓜慢说官方网站](https://www.pkslow.com/tags/terraform101)和[GitHub](https://github.com/LarryDpk/terraform-101)两个地方同步更新,书中的示例代码也是放在GitHub上,方便大家参考查看。 2 | 3 | --- 4 | 5 | 6 | 7 | # Terraform的函数 8 | 9 | Terraform为了让大家在表达式上可以更加灵活方便地进行计算,提供了大量的内置函数(Function)。目前并不支持自定义函数,只能使用Terraform自带的。使用函数的格式也很简单,直接写函数名+参数即可。如下面的函数为取最大值: 10 | 11 | ```hcl 12 | > max(34, 45, 232, 25) 13 | 232 14 | ``` 15 | 16 | 这里把函数单独列成一章不是因为它很难理解,而因为它很常用,值得把这些函数梳理一下,以便查询使用吧。 17 | 18 | 19 | 20 | # 数值计算函数 21 | 22 | 绝对值abs: 23 | 24 | ```hcl 25 | > abs(5) 26 | 5 27 | > abs(-3.1415926) 28 | 3.1415926 29 | > abs(0) 30 | 0 31 | ``` 32 | 33 | 34 | 35 | 返回大于等于该数值的最小整数: 36 | 37 | ```hcl 38 | > ceil(3) 39 | 3 40 | > ceil(3.1) 41 | 4 42 | > ceil(2.9) 43 | 3 44 | ``` 45 | 46 | 47 | 48 | 小于等于该数值的最大整数: 49 | 50 | ```hcl 51 | > floor(6) 52 | 6 53 | > floor(6.9) 54 | 6 55 | > floor(5.34) 56 | 5 57 | ``` 58 | 59 | 60 | 61 | 对数函数: 62 | 63 | ```hcl 64 | > log(16, 2) 65 | 4 66 | > log(9, 3) 67 | 2.0000000000000004 68 | ``` 69 | 70 | 71 | 72 | 指数函数: 73 | 74 | ```hcl 75 | > pow(6, 2) 76 | 36 77 | > pow(6, 1) 78 | 6 79 | > pow(6, 0) 80 | 1 81 | ``` 82 | 83 | 84 | 85 | 最大值、最小值: 86 | 87 | ```hcl 88 | > max(2, 98, 75, 4) 89 | 98 90 | > min(2, 98, 75, 4) 91 | 2 92 | ``` 93 | 94 | 95 | 96 | 字符串转换成整数,第二个参数为进制: 97 | 98 | ```hcl 99 | > parseint("16", 10) 100 | 16 101 | > parseint("16", 16) 102 | 22 103 | > parseint("FF", 16) 104 | 255 105 | > parseint("1010", 2) 106 | 10 107 | ``` 108 | 109 | 110 | 111 | 信号量函数: 112 | 113 | ```hcl 114 | > signum(6) 115 | 1 116 | > signum(-6) 117 | -1 118 | > signum(0) 119 | 0 120 | ``` 121 | 122 | 123 | 124 | # 字符串函数 125 | 126 | 删去换行,在从文件中读取文本时非常有用: 127 | 128 | ```hcl 129 | > chomp("www.pkslow.com") 130 | "www.pkslow.com" 131 | > chomp("www.pkslow.com\n") 132 | "www.pkslow.com" 133 | > chomp("www.pkslow.com\n\n") 134 | "www.pkslow.com" 135 | > chomp("www.pkslow.com\n\n\r") 136 | "www.pkslow.com" 137 | > chomp("www.pkslow.com\n\n\ra") 138 | < format("Hi, %s!", "Larry") 151 | "Hi, Larry!" 152 | 153 | > format("My name is %s, I'm %d", "Larry", 18) 154 | "My name is Larry, I'm 18" 155 | 156 | > format("The reuslt is %.2f", 3) 157 | "The reuslt is 3.00" 158 | 159 | > format("The reuslt is %.2f", 3.1415) 160 | "The reuslt is 3.14" 161 | 162 | > format("The reuslt is %8.2f", 3.1415) 163 | "The reuslt is 3.14" 164 | ``` 165 | 166 | 167 | 168 | 遍历格式化列表: 169 | 170 | ```hcl 171 | > formatlist("My name is %s, I'm %d %s.", ["Larry", "Jeremy", "Tailor"], [18, 28, 33], "in 2022") 172 | tolist([ 173 | "My name is Larry, I'm 18 in 2022.", 174 | "My name is Jeremy, I'm 28 in 2022.", 175 | "My name is Tailor, I'm 33 in 2022.", 176 | ]) 177 | ``` 178 | 179 | 参数可以是List,还可以是单个变量。 180 | 181 | 182 | 183 | 字符串连接: 184 | 185 | ```hcl 186 | > join(".", ["www", "pkslow", "com"]) 187 | "www.pkslow.com" 188 | > join(", ", ["Larry", "Pkslow", "JJ"]) 189 | "Larry, Pkslow, JJ" 190 | ``` 191 | 192 | 193 | 194 | 大小写字母转换: 195 | 196 | ```hcl 197 | > lower("Larry Nanhua DENG") 198 | "larry nanhua deng" 199 | > upper("Larry Nanhua DENG") 200 | "LARRY NANHUA DENG" 201 | ``` 202 | 203 | 204 | 205 | 首字母大写: 206 | 207 | ```hcl 208 | > title("larry") 209 | "Larry" 210 | ``` 211 | 212 | 213 | 214 | 替换: 215 | 216 | ```hcl 217 | > replace("www.larrydpk.com", "larrydpk", "pkslow") 218 | "www.pkslow.com" 219 | > replace("hello larry", "/la.*y/", "pkslow") 220 | "hello pkslow" 221 | ``` 222 | 223 | 224 | 225 | 226 | 227 | 分割: 228 | 229 | ```hcl 230 | > split(".", "www.pklow.com") 231 | tolist([ 232 | "www", 233 | "pklow", 234 | "com", 235 | ]) 236 | ``` 237 | 238 | 239 | 240 | 反转: 241 | 242 | ```hcl 243 | > strrev("pkslow") 244 | "wolskp" 245 | ``` 246 | 247 | 248 | 249 | 截取: 250 | 251 | ```hcl 252 | > substr("Larry Deng", 0, 5) 253 | "Larry" 254 | > substr("Larry Deng", -4, -1) 255 | "Deng" 256 | ``` 257 | 258 | 259 | 260 | 去除头尾某些特定字符,注意这里只要有对应字符就会删除: 261 | 262 | ```hcl 263 | > trim("?!what?!!!!!", "?!") 264 | "what" 265 | > trim("abaaaaabbLarry Dengaab", "ab") 266 | "Larry Deng" 267 | ``` 268 | 269 | 270 | 271 | 去除头尾特定字符串,注意与上面的区别: 272 | 273 | ```hcl 274 | > trimsuffix("?!what?!!!!!", "!!!") 275 | "?!what?!!" 276 | > trimprefix("?!what?!!!!!", "?!") 277 | "what?!!!!!" 278 | ``` 279 | 280 | 281 | 282 | 去除头尾的空格、换行等空串: 283 | 284 | ```hcl 285 | > trimspace(" Larry Deng \n\r") 286 | "Larry Deng" 287 | ``` 288 | 289 | 290 | 291 | 正则匹配,下面的例子是匹配第一个和匹配所有: 292 | 293 | ```hcl 294 | > regex("[a-z\\.]+", "2021www.pkslow.com2022larry deng 31415926") 295 | "www.pkslow.com" 296 | > regexall("[a-z\\.]+", "2021www.pkslow.com2022larry deng 31415926") 297 | tolist([ 298 | "www.pkslow.com", 299 | "larry", 300 | "deng", 301 | ]) 302 | ``` 303 | 304 | 更多正则匹配语法可参考:https://www.terraform.io/language/functions/regex 305 | 306 | 307 | 308 | # 集合类函数 309 | 310 | `alltrue`:判断列表是否全为真,空列表直接返回true。只能是bool类型或者对应的字符串。 311 | 312 | ```hcl 313 | > alltrue([true, "true"]) 314 | true 315 | > alltrue([true, "true", false]) 316 | false 317 | > alltrue([]) 318 | true 319 | > alltrue([1]) 320 | ╷ 321 | │ Error: Invalid function argument 322 | │ 323 | │ on line 1: 324 | │ (source code not available) 325 | │ 326 | │ Invalid value for "list" parameter: element 0: bool required. 327 | ``` 328 | 329 | 330 | 331 | `anytrue`:判断列表是否有真,只要有一个为真就返回true。空列表为false。 332 | 333 | ```hcl 334 | > anytrue([true]) 335 | true 336 | > anytrue([true, false]) 337 | true 338 | > anytrue([false, false]) 339 | false 340 | > anytrue([]) 341 | false 342 | ``` 343 | 344 | 345 | 346 | `chunklist`分片:根据分片数来对列表进行切分。 347 | 348 | ```hcl 349 | > chunklist(["www", "pkslow", "com", "Larry", "Deng"], 3) 350 | tolist([ 351 | tolist([ 352 | "www", 353 | "pkslow", 354 | "com", 355 | ]), 356 | tolist([ 357 | "Larry", 358 | "Deng", 359 | ]), 360 | ]) 361 | ``` 362 | 363 | 364 | 365 | `coalesce`返回第一个非空元素: 366 | 367 | ```hcl 368 | > coalesce("", "a", "b") 369 | "a" 370 | > coalesce("", "", "b") 371 | "b" 372 | ``` 373 | 374 | 375 | 376 | `coalescelist`返回第一个非空列表: 377 | 378 | ```hcl 379 | > coalescelist([], ["pkslow"]) 380 | [ 381 | "pkslow", 382 | ] 383 | ``` 384 | 385 | 386 | 387 | 从字符串列表里把空的去掉: 388 | 389 | ```hcl 390 | > compact(["", "www", "", "pkslow", "com"]) 391 | tolist([ 392 | "www", 393 | "pkslow", 394 | "com", 395 | ]) 396 | ``` 397 | 398 | 399 | 400 | `concat`连接多个列表: 401 | 402 | ```hcl 403 | > concat([1, 2, 3], [4, 5, 6]) 404 | [ 405 | 1, 406 | 2, 407 | 3, 408 | 4, 409 | 5, 410 | 6, 411 | ] 412 | ``` 413 | 414 | 415 | 416 | `contains`判断是否存在某个元素: 417 | 418 | ```hcl 419 | > contains(["www", "pkslow", "com"], "pkslow") 420 | true 421 | > contains(["www", "pkslow", "com"], "Larry") 422 | false 423 | ``` 424 | 425 | 426 | 427 | `distinct`去除重复元素: 428 | 429 | ```hcl 430 | > distinct([1, 2, 2, 1, 3, 8, 1, 10]) 431 | tolist([ 432 | 1, 433 | 2, 434 | 3, 435 | 8, 436 | 10, 437 | ]) 438 | ``` 439 | 440 | 441 | 442 | `element`获取列表的某个元素: 443 | 444 | ```hcl 445 | > element(["a", "b", "c"], 1) 446 | "b" 447 | > element(["a", "b", "c"], 2) 448 | "c" 449 | > element(["a", "b", "c"], 3) 450 | "a" 451 | > element(["a", "b", "c"], 4) 452 | "b" 453 | ``` 454 | 455 | 456 | 457 | `flatten`把内嵌的列表都展开成一个列表: 458 | 459 | ```hcl 460 | > flatten([1, 2, 3, [1], [[6]]]) 461 | [ 462 | 1, 463 | 2, 464 | 3, 465 | 1, 466 | 6, 467 | ] 468 | ``` 469 | 470 | 471 | 472 | `index`获取列表中的元素的索引值: 473 | 474 | ```hcl 475 | > index(["www", "pkslow", "com"], "pkslow") 476 | 1 477 | ``` 478 | 479 | 480 | 481 | `keys`获取map的所有key值: 482 | 483 | ```hcl 484 | > keys({name="Larry", age=18, webSite="www.pkslow.com"}) 485 | [ 486 | "age", 487 | "name", 488 | "webSite", 489 | ] 490 | ``` 491 | 492 | 493 | 494 | `values`获取map的value值: 495 | 496 | ```hcl 497 | > values({name="Larry", age=18, webSite="www.pkslow.com"}) 498 | [ 499 | 18, 500 | "Larry", 501 | "www.pkslow.com", 502 | ] 503 | ``` 504 | 505 | 506 | 507 | `length`获取字符串、列表、Map等的长度: 508 | 509 | ```hcl 510 | > length([]) 511 | 0 512 | > length(["pkslow"]) 513 | 1 514 | > length(["pkslow", "com"]) 515 | 2 516 | > length({pkslow = "com"}) 517 | 1 518 | > length("pkslow") 519 | 6 520 | ``` 521 | 522 | 523 | 524 | `lookup(map, key, default)`根据key值在map中找到对应的value值,如果没有则返回默认值: 525 | 526 | ```hcl 527 | > lookup({name = "Larry", age = 18}, "age", 1) 528 | 18 529 | > lookup({name = "Larry", age = 18}, "myAge", 1) 530 | 1 531 | ``` 532 | 533 | 534 | 535 | `matchkeys(valueslist, keyslist, searchset)`对key值进行匹配。匹配到key值后,返回对应的Value值。 536 | 537 | ```hcl 538 | > matchkeys(["a", "b", "c", "d"], [1, 2, 3, 4], [2, 4]) 539 | tolist([ 540 | "b", 541 | "d", 542 | ]) 543 | ``` 544 | 545 | 546 | 547 | `merge`合并Map,key相同的会被最后的覆盖: 548 | 549 | ```hcl 550 | > merge({name = "Larry", webSite = "pkslow.com"}, {age = 18}) 551 | { 552 | "age" = 18 553 | "name" = "Larry" 554 | "webSite" = "pkslow.com" 555 | } 556 | > merge({name = "Larry", webSite = "pkslow.com"}, {age = 18}, {age = 13}) 557 | { 558 | "age" = 13 559 | "name" = "Larry" 560 | "webSite" = "pkslow.com" 561 | } 562 | ``` 563 | 564 | 565 | 566 | `one`取集合的一个元素,如果为空则返回null;如果只有一个元素,则返回该元素;如果多个元素,则报错: 567 | 568 | ```hcl 569 | > one([]) 570 | null 571 | > one(["pkslow"]) 572 | "pkslow" 573 | > one(["pkslow", "com"]) 574 | ╷ 575 | │ Error: Invalid function argument 576 | │ 577 | │ on line 1: 578 | │ (source code not available) 579 | │ 580 | │ Invalid value for "list" parameter: must be a list, set, or tuple value with either zero or one elements. 581 | ╵ 582 | ``` 583 | 584 | 585 | 586 | `range`生成顺序列表: 587 | 588 | ```hcl 589 | range(max) 590 | range(start, limit) 591 | range(start, limit, step) 592 | 593 | > range(3) 594 | tolist([ 595 | 0, 596 | 1, 597 | 2, 598 | ]) 599 | > range(1, 6) 600 | tolist([ 601 | 1, 602 | 2, 603 | 3, 604 | 4, 605 | 5, 606 | ]) 607 | > range(1, 6, 2) 608 | tolist([ 609 | 1, 610 | 3, 611 | 5, 612 | ]) 613 | ``` 614 | 615 | 616 | 617 | `reverse`反转列表: 618 | 619 | ```hcl 620 | > reverse([1, 2, 3, 4]) 621 | [ 622 | 4, 623 | 3, 624 | 2, 625 | 1, 626 | ] 627 | ``` 628 | 629 | 630 | 631 | `setintersection`对set求交集: 632 | 633 | ```hcl 634 | > setintersection([1, 2, 3], [2, 3, 4], [2, 3, 6]) 635 | toset([ 636 | 2, 637 | 3, 638 | ]) 639 | ``` 640 | 641 | 642 | 643 | `setproduct`列出所有组合可能: 644 | 645 | ```hcl 646 | > setproduct(["Larry", "Harry"], ["Deng", "Potter"]) 647 | tolist([ 648 | [ 649 | "Larry", 650 | "Deng", 651 | ], 652 | [ 653 | "Larry", 654 | "Potter", 655 | ], 656 | [ 657 | "Harry", 658 | "Deng", 659 | ], 660 | [ 661 | "Harry", 662 | "Potter", 663 | ], 664 | ]) 665 | ``` 666 | 667 | 668 | 669 | `setsubtract`:set的减法 670 | 671 | ```hcl 672 | > setsubtract([1, 2, 3], [3, 4]) 673 | toset([ 674 | 1, 675 | 2, 676 | ]) 677 | 678 | # 求不同 679 | > setunion(setsubtract(["a", "b", "c"], ["a", "c", "d"]), setsubtract(["a", "c", "d"], ["a", "b", "c"])) 680 | [ 681 | "b", 682 | "d", 683 | ] 684 | ``` 685 | 686 | 687 | 688 | `setunion`:set的加法 689 | 690 | ```hcl 691 | > setunion([1, 2, 3], [3, 4]) 692 | toset([ 693 | 1, 694 | 2, 695 | 3, 696 | 4, 697 | ]) 698 | ``` 699 | 700 | 701 | 702 | `slice(list, startindex, endindex)`截取列表部分,包括startindex,但不包括endindex: 703 | 704 | ```hcl 705 | > slice(["a", "b", "c", "d", "e"], 1, 4) 706 | [ 707 | "b", 708 | "c", 709 | "d", 710 | ] 711 | ``` 712 | 713 | 714 | 715 | `sort`对列表中的字符串进行排序,要注意如果输入的是数字,会先转化为字符串再排序: 716 | 717 | ```hcl 718 | > sort(["larry", "pkslow", "com", "deng"]) 719 | tolist([ 720 | "com", 721 | "deng", 722 | "larry", 723 | "pkslow", 724 | ]) 725 | > sort([3, 6, 1, 9, 12, 79, 22]) 726 | tolist([ 727 | "1", 728 | "12", 729 | "22", 730 | "3", 731 | "6", 732 | "79", 733 | "9", 734 | ]) 735 | ``` 736 | 737 | 738 | 739 | `sum`求和: 740 | 741 | ```hcl 742 | > sum([3, 1.2, 9, 17.3, 2.2]) 743 | 32.7 744 | ``` 745 | 746 | 747 | 748 | `transpose`对Map的key和value进行换位: 749 | 750 | ```hcl 751 | > transpose({"a" = ["1", "2"], "b" = ["2", "3"]}) 752 | tomap({ 753 | "1" = tolist([ 754 | "a", 755 | ]) 756 | "2" = tolist([ 757 | "a", 758 | "b", 759 | ]) 760 | "3" = tolist([ 761 | "b", 762 | ]) 763 | }) 764 | ``` 765 | 766 | 767 | 768 | 769 | 770 | `zipmap`根据key和value的列表按一对一关系生成Map: 771 | 772 | ```hcl 773 | > zipmap(["age", "name"], [18, "Larry Deng"]) 774 | { 775 | "age" = 18 776 | "name" = "Larry Deng" 777 | } 778 | ``` 779 | 780 | 781 | 782 | # 加密解密 783 | 784 | Base64: 785 | 786 | ```hcl 787 | > base64encode("pkslow") 788 | "cGtzbG93" 789 | > base64decode("cGtzbG93") 790 | "pkslow" 791 | > textencodebase64("pkslow", "UTF-8") 792 | "cGtzbG93" 793 | > textdecodebase64("cGtzbG93", "UTF-8") 794 | "pkslow" 795 | ``` 796 | 797 | 798 | 799 | csv文本解析: 800 | 801 | ```hcl 802 | > csvdecode("seq,name,age\n1,larry,18\n2,pkslow,3\n3,Jeremy,29") 803 | tolist([ 804 | { 805 | "age" = "18" 806 | "name" = "larry" 807 | "seq" = "1" 808 | }, 809 | { 810 | "age" = "3" 811 | "name" = "pkslow" 812 | "seq" = "2" 813 | }, 814 | { 815 | "age" = "29" 816 | "name" = "Jeremy" 817 | "seq" = "3" 818 | }, 819 | ]) 820 | ``` 821 | 822 | 823 | 824 | Json解析: 825 | 826 | ```hcl 827 | > jsonencode({"name"="Larry", "age"=18}) 828 | "{\"age\":18,\"name\":\"Larry\"}" 829 | > jsondecode("{\"age\":18,\"name\":\"Larry\"}") 830 | { 831 | "age" = 18 832 | "name" = "Larry" 833 | } 834 | ``` 835 | 836 | 837 | 838 | URL: 839 | 840 | ```hcl 841 | > urlencode("Larry Deng/a/:/./@") 842 | "Larry+Deng%2Fa%2F%3A%2F.%2F%40" 843 | ``` 844 | 845 | 846 | 847 | YAML: 848 | 849 | ```hcl 850 | > yamlencode({"a":"b", "c":"d"}) 851 | "a": "b" 852 | "c": "d" 853 | 854 | > yamlencode({"foo":[1, 2, 3], "bar": "baz"}) 855 | "bar": "baz" 856 | "foo": 857 | - 1 858 | - 2 859 | - 3 860 | 861 | > yamlencode({"foo":[1, {"a":"b","c":"d"}, 3], "bar": "baz"}) 862 | "bar": "baz" 863 | "foo": 864 | - 1 865 | - "a": "b" 866 | "c": "d" 867 | - 3 868 | > yamldecode("hello: world") 869 | { 870 | "hello" = "world" 871 | } 872 | 873 | > yamldecode("true") 874 | true 875 | 876 | > yamldecode("{a: &foo [1, 2, 3], b: *foo}") 877 | { 878 | "a" = [ 879 | 1, 880 | 2, 881 | 3, 882 | ] 883 | "b" = [ 884 | 1, 885 | 2, 886 | 3, 887 | ] 888 | } 889 | ``` 890 | 891 | 892 | 893 | 文件处理: 894 | 895 | 获取绝对路径: 896 | 897 | ```hcl 898 | > abspath(path.root) 899 | "/Users/larry" 900 | ``` 901 | 902 | 903 | 904 | 获取路径中的目录,或者是文件名: 905 | 906 | ```hcl 907 | > dirname("/home/larry/soft/terraform") 908 | "/home/larry/soft" 909 | > dirname("/home/larry/soft/terraform/") 910 | "/home/larry/soft/terraform" 911 | > basename("/home/larry/soft/terraform") 912 | "terraform" 913 | > basename("/home/larry/soft/terraform/") 914 | "terraform" 915 | ``` 916 | 917 | 918 | 919 | 判断文件是否存在,并获取文件内容: 920 | 921 | ```hcl 922 | > fileexists("/Users/larry/.bash_profile") 923 | true 924 | > file("/Users/larry/.bash_profile") 925 | > filebase64("/Users/larry/.bash_profile") 926 | ``` 927 | 928 | 929 | 930 | 根据模式匹配所有文件: 931 | 932 | ```hcl 933 | > fileset("/Users/larry", "*.bash*") 934 | toset([ 935 | ".bash_history", 936 | ".bash_profile", 937 | ".bash_profile.backup", 938 | ]) 939 | ``` 940 | 941 | 942 | 943 | [`templatefile(path, vars)`模板化文件](https://www.terraform.io/language/functions/templatefile):指定文件和变量,把变量值替换掉模板中的变量。 944 | 945 | 946 | 947 | # 时间函数 948 | 949 | 获取当前时间,并格式化显示,格式请参考:https://www.terraform.io/language/functions/formatdate 950 | 951 | ```hcl 952 | > formatdate("YYYY-MM-DD hh:mm:ss / D MMMM YYYY", timestamp()) 953 | "2022-03-05 08:25:48 / 5 March 2022" 954 | > formatdate("EEEE, DD-MMM-YY hh:mm:ss ZZZ", "2018-01-02T23:12:01Z") 955 | "Tuesday, 02-Jan-18 23:12:01 UTC" 956 | ``` 957 | 958 | 959 | 960 | 时间加减: 961 | 962 | ```hcl 963 | > timeadd(timestamp(), "24h") 964 | "2022-03-06T08:28:52Z" 965 | > timeadd(timestamp(), "-24h10m") 966 | "2022-03-04T08:19:08Z" 967 | ``` 968 | 969 | 支持的单位有:`"ns"`, `"us"` (or `"µs"`), `"ms"`, `"s"`, `"m"`, and `"h"`. 970 | 971 | 972 | 973 | 974 | 975 | 976 | 977 | # 其它 978 | 979 | 加密: 980 | 981 | ```hcl 982 | > md5("www.pkslow.com") 983 | "97e164b60faf4d7875c2a8a5bc3f2245" 984 | ``` 985 | 986 | 987 | 988 | UUID: 989 | 990 | ```hcl 991 | > uuid() 992 | "049bf418-15d1-e034-28db-92945067dcf6" 993 | ``` 994 | 995 | 996 | 997 | IP: 998 | 999 | ```hcl 1000 | > cidrsubnet("172.16.0.0/12", 4, 2) 1001 | "172.18.0.0/16" 1002 | ``` 1003 | 1004 | 1005 | 1006 | 更多请参考官网。 1007 | -------------------------------------------------------------------------------- /Terraform在公有云Azure上的应用.md: -------------------------------------------------------------------------------- 1 | > 《Terraform 101 从入门到实践》这本小册在[南瓜慢说官方网站](https://www.pkslow.com/tags/terraform101)和[GitHub](https://github.com/LarryDpk/terraform-101)两个地方同步更新,书中的示例代码也是放在GitHub上,方便大家参考查看。 2 | 3 | --- 4 | 5 | 6 | 7 | 8 | 9 | # 简介 10 | 11 | `Azure`是微软的公有云,它提供了一些免费的资源,具体可以查看: https://azure.microsoft.com/en-us/free/ 12 | 13 | 本章将介绍如何通过`Terraform`来使用`Azure`的云资源。 14 | 15 | 16 | 17 | # 注册Azure账号 18 | 19 | 首先要注册一个Azure账号,我选择用GitHub账号登陆,免得又记多一个密码。 20 | 21 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/azure/create-azure-account.png) 22 | 23 | 24 | 25 | 26 | 27 | 跳到GitHub,同意即可: 28 | 29 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/azure/create-azure-account.with-github.png) 30 | 31 | 32 | 33 | 创建账号时,有一些信息要填,特别是邮箱和手机号比较关键: 34 | 35 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/azure/create-azure-account.profile.png) 36 | 37 | 38 | 39 | 40 | 41 | 同时还需要一张Visa或Master卡,我是有一张Visa的卡,填好后会有一个0元的扣费,不要担心。下面Cardholder Name我填的中文名字,注册成功了。 42 | 43 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/azure/create-azure-account.visa-card.png) 44 | 45 | 46 | 47 | 0元扣费成功后,表示卡是正常的,就可以成功注册了,注册后就可以到[Portal](https://portal.azure.com/?quickstart=true#view/Microsoft_Azure_Resources/QuickstartCenterBlade)查看了。 48 | 49 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/azure/create-azure-account.portal.png) 50 | 51 | 52 | 53 | # 手动部署虚拟机 54 | 55 | 为了体验一下Azure,我们先手动创建一个虚拟机,操作入口如下: 56 | 57 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/azure/create-vm.add-vm.png) 58 | 59 | 60 | 61 | 62 | 63 | 需要填写一些配置信息,如主机名、区域、镜像、网络端口等,按需要我打开了22/80/443端口。 64 | 65 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/azure/create-vm.config.png) 66 | 67 | 68 | 69 | 70 | 71 | 完成配置后,点击创建,提示要下载密钥对,必须要在创建的时候下载: 72 | 73 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/azure/create-vm.download-ssh-key.png) 74 | 75 | 76 | 77 | 创建完资源后,可以在虚拟机列表查看: 78 | 79 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/azure/create-vm.vm-list.png) 80 | 81 | 根据用户名和公网IP,我们可以ssh连接到服务器。需要给密钥文件修改权限,太大是不行的,会报错。 82 | 83 | ```bash 84 | $ chmod 400 ~/Downloads/pksow-azure.pem 85 | ``` 86 | 87 | 88 | 89 | 然后通过下面命令连接: 90 | 91 | ```bash 92 | $ ssh azureuser@20.2.85.137 -i ~/Downloads/pksow-azure.pem 93 | Welcome to Ubuntu 22.04.1 LTS (GNU/Linux 5.15.0-1030-azure x86_64) 94 | 95 | * Documentation: https://help.ubuntu.com 96 | * Management: https://landscape.canonical.com 97 | * Support: https://ubuntu.com/advantage 98 | 99 | System load: 0.01513671875 Processes: 109 100 | Usage of /: 4.9% of 28.89GB Users logged in: 0 101 | Memory usage: 31% IPv4 address for eth0: 10.0.0.4 102 | Swap usage: 0% 103 | 104 | 0 updates can be applied immediately. 105 | 106 | 107 | 108 | The programs included with the Ubuntu system are free software; 109 | the exact distribution terms for each program are described in the 110 | individual files in /usr/share/doc/*/copyright. 111 | 112 | Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by 113 | applicable law. 114 | 115 | To run a command as administrator (user "root"), use "sudo ". 116 | See "man sudo_root" for details. 117 | 118 | azureuser@pkslow:~$ free 119 | total used free shared buff/cache available 120 | Mem: 928460 261816 288932 4140 377712 533872 121 | Swap: 0 0 0 122 | azureuser@pkslow:~$ df -h 123 | Filesystem Size Used Avail Use% Mounted on 124 | /dev/root 29G 1.5G 28G 5% / 125 | tmpfs 454M 0 454M 0% /dev/shm 126 | tmpfs 182M 1.1M 181M 1% /run 127 | tmpfs 5.0M 0 5.0M 0% /run/lock 128 | /dev/sda15 105M 5.3M 100M 5% /boot/efi 129 | /dev/sdb1 3.9G 28K 3.7G 1% /mnt 130 | tmpfs 91M 4.0K 91M 1% /run/user/1000 131 | ``` 132 | 133 | 134 | 135 | 136 | 137 | # 通过azure-cli创建虚拟机 138 | 139 | 140 | 141 | ## 安装azure-cli 142 | 143 | 我的电脑是MacOS,安装如下: 144 | 145 | ```bash 146 | $ brew update-reset 147 | 148 | $ brew install azure-cli 149 | 150 | $ which az 151 | /usr/local/bin/az 152 | 153 | $ az version 154 | { 155 | "azure-cli": "2.44.1", 156 | "azure-cli-core": "2.44.1", 157 | "azure-cli-telemetry": "1.0.8", 158 | "extensions": {} 159 | } 160 | ``` 161 | 162 | 163 | 164 | 其它系统请参考: https://learn.microsoft.com/en-us/cli/azure/install-azure-cli 165 | 166 | 167 | 168 | ## 权限 169 | 170 | 通过命令行操作Azure的资源,必然是需要权限的,我们可以通过密码,还可以通过Service Principal等方式来登陆。我们主要使用Service Principal的方式来授权。因此我们先在Portal上创建。 171 | 172 | 在左侧菜单选择`Azure Active Directory`,选择`应用注册`,点击`新注册`: 173 | 174 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/azure/create-service-principal.app-reg.png) 175 | 176 | 177 | 178 | 注册应用程序: 179 | 180 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/azure/create-service-principal.app-reg2.png) 181 | 182 | 183 | 184 | 添加密码: 185 | 186 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/azure/create-service-principal.add-client-password.png) 187 | 188 | 189 | 190 | 191 | 192 | 设置说明和时长: 193 | 194 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/azure/create-service-principal.config-password.png) 195 | 196 | 197 | 198 | 199 | 200 | 创建完后要马上记下密码,后面无法再获取密码值: 201 | 202 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/azure/create-service-principal.markdown-password.png) 203 | 204 | 205 | 206 | ## 查看租户 207 | 208 | 需要查看租户ID,或创建租户: 209 | 210 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/azure/tenant-info.png) 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | ## 分配角色 219 | 220 | 到订阅管理界面: [Subscriptions page in Azure portal](https://portal.azure.com/#blade/Microsoft_Azure_Billing/SubscriptionsBlade),查看订阅列表: 221 | 222 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/azure/subscription-list.png) 223 | 224 | 点进去后,可以管理访问控制: 225 | 226 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/azure/subscription-access.png) 227 | 228 | 229 | 230 | 把之前创建的Service Principal加进来,分配特定角色: 231 | 232 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/azure/subscription-add-role.png) 233 | 234 | 235 | 236 | 237 | 238 | 选择对应的Service Principal: 239 | 240 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/azure/subscription-add-sp.png) 241 | 242 | 243 | 244 | 245 | 246 | ## 命令行登陆 247 | 248 | 完成以上操作后,就可以通过命令行来登陆Azure了: 249 | 250 | ```bash 251 | $ az login --service-principal -u f01d69bf-8ff3-4043-9275-3e0c4de54884 -p B0N8Q~PQu6hTJkBTS5xxxxxxxx******** --tenant 2951528a-e359-4846-9817-ec3ebc2664d4 252 | [ 253 | { 254 | "cloudName": "AzureCloud", 255 | "homeTenantId": "2951528a-e359-4846-9817-ec3ebc2664d4", 256 | "id": "cd7921d5-9ba9-45db-bfba-1c397fcaaba3", 257 | "isDefault": true, 258 | "managedByTenants": [], 259 | "name": "Free Trial", 260 | "state": "Enabled", 261 | "tenantId": "2951528a-e359-4846-9817-ec3ebc2664d4", 262 | "user": { 263 | "name": "f01d69bf-8ff3-4043-9275-3e0c4de54884", 264 | "type": "servicePrincipal" 265 | } 266 | } 267 | ] 268 | ``` 269 | 270 | `-u`是注册应用的ID; 271 | 272 | `-p`就是之前要记下的密码; 273 | 274 | `--tenant`就是租户ID; 275 | 276 | 277 | 278 | 查询之前创建的VM,成功: 279 | 280 | ```bash 281 | $ az vm list -g test --output table 282 | Name ResourceGroup Location Zones 283 | ------ --------------- ---------- ------- 284 | pkslow test eastasia 1 285 | ``` 286 | 287 | 288 | 289 | ## 创建vm 290 | 291 | 通过命令行创建vm如下: 292 | 293 | ```bash 294 | $ az vm create --resource-group 'test' --name 'pkslow2' --image 'canonical:0001-com-ubuntu-server-jammy:22_04-lts:22.04.202301100' --admin-username 'larry' --admin-password 'Pa!!!ss123' --location 'eastasia' 295 | 296 | { 297 | "fqdns": "", 298 | "id": "/subscriptions/cd7921d5-9ba9-45db-bfba-1c397fcaaba3/resourceGroups/test/providers/Microsoft.Compute/virtualMachines/pkslow2", 299 | "location": "eastasia", 300 | "macAddress": "60-45-BD-57-30-C1", 301 | "powerState": "VM running", 302 | "privateIpAddress": "10.0.0.5", 303 | "publicIpAddress": "20.187.85.53", 304 | "resourceGroup": "test", 305 | "zones": "" 306 | } 307 | 308 | ``` 309 | 310 | 查询后成功创建,已经有2台虚拟机在运行: 311 | 312 | ```bash 313 | $ az vm list -g test --output table 314 | Name ResourceGroup Location Zones 315 | ------- --------------- ---------- ------- 316 | pkslow test eastasia 1 317 | pkslow2 test eastasia 318 | ``` 319 | 320 | 321 | 322 | # 用Terraform创建vm 323 | 324 | ## 权限环境变量设置 325 | 326 | 当我们使用Terraform来操作Azure时,同样也是需要权限的,配置以下环境变量即可。这些值在前面的内容已经讲过了。 327 | 328 | ```bash 329 | export ARM_SUBSCRIPTION_ID="" 330 | export ARM_TENANT_ID="" 331 | export ARM_CLIENT_ID="" 332 | export ARM_CLIENT_SECRET="" 333 | ``` 334 | 335 | 336 | 337 | ## 插件和版本 338 | 339 | 配置Terraform和插件的版本: 340 | 341 | ```hcl 342 | terraform { 343 | required_version = ">= 1.1.3" 344 | required_providers { 345 | 346 | azurerm = { 347 | source = "hashicorp/azurerm" 348 | version = "3.38.0" 349 | } 350 | } 351 | } 352 | ``` 353 | 354 | 355 | 356 | ## 创建vm 357 | 358 | 通过`azurerm_virtual_machine`来创建VM资源: 359 | 360 | ```hcl 361 | provider "azurerm" { 362 | features {} 363 | } 364 | 365 | variable "prefix" { 366 | default = "pkslow-azure" 367 | } 368 | 369 | resource "azurerm_resource_group" "example" { 370 | name = "${var.prefix}-resources" 371 | location = "West Europe" 372 | } 373 | 374 | resource "azurerm_virtual_network" "main" { 375 | name = "${var.prefix}-network" 376 | address_space = ["10.0.0.0/16"] 377 | location = azurerm_resource_group.example.location 378 | resource_group_name = azurerm_resource_group.example.name 379 | } 380 | 381 | resource "azurerm_subnet" "internal" { 382 | name = "internal" 383 | resource_group_name = azurerm_resource_group.example.name 384 | virtual_network_name = azurerm_virtual_network.main.name 385 | address_prefixes = ["10.0.2.0/24"] 386 | } 387 | 388 | resource "azurerm_network_interface" "main" { 389 | name = "${var.prefix}-nic" 390 | location = azurerm_resource_group.example.location 391 | resource_group_name = azurerm_resource_group.example.name 392 | 393 | ip_configuration { 394 | name = "testconfiguration1" 395 | subnet_id = azurerm_subnet.internal.id 396 | private_ip_address_allocation = "Dynamic" 397 | } 398 | } 399 | 400 | resource "azurerm_virtual_machine" "main" { 401 | name = "${var.prefix}-vm" 402 | location = azurerm_resource_group.example.location 403 | resource_group_name = azurerm_resource_group.example.name 404 | network_interface_ids = [azurerm_network_interface.main.id] 405 | vm_size = "Standard_DS1_v2" 406 | 407 | # Uncomment this line to delete the OS disk automatically when deleting the VM 408 | # delete_os_disk_on_termination = true 409 | 410 | # Uncomment this line to delete the data disks automatically when deleting the VM 411 | # delete_data_disks_on_termination = true 412 | 413 | storage_image_reference { 414 | publisher = "Canonical" 415 | offer = "0001-com-ubuntu-server-jammy" 416 | sku = "22_04-lts" 417 | version = "22.04.202301100" 418 | } 419 | storage_os_disk { 420 | name = "myosdisk1" 421 | caching = "ReadWrite" 422 | create_option = "FromImage" 423 | managed_disk_type = "Standard_LRS" 424 | } 425 | os_profile { 426 | computer_name = "hostname" 427 | admin_username = "larry" 428 | admin_password = "Password1234!" 429 | } 430 | os_profile_linux_config { 431 | disable_password_authentication = false 432 | } 433 | tags = { 434 | environment = "staging" 435 | } 436 | } 437 | ``` 438 | 439 | 440 | 441 | 然后我们执行初始化,会下载Azure的Terraform插件: 442 | 443 | ```bash 444 | $ terraform init 445 | 446 | Initializing the backend... 447 | 448 | Initializing provider plugins... 449 | - Finding hashicorp/azurerm versions matching "3.38.0"... 450 | - Installing hashicorp/azurerm v3.38.0... 451 | - Installed hashicorp/azurerm v3.38.0 (signed by HashiCorp) 452 | 453 | Terraform has created a lock file .terraform.lock.hcl to record the provider 454 | selections it made above. Include this file in your version control repository 455 | so that Terraform can guarantee to make the same selections by default when 456 | you run "terraform init" in the future. 457 | 458 | Terraform has been successfully initialized! 459 | 460 | You may now begin working with Terraform. Try running "terraform plan" to see 461 | any changes that are required for your infrastructure. All Terraform commands 462 | should now work. 463 | 464 | If you ever set or change modules or backend configuration for Terraform, 465 | rerun this command to reinitialize your working directory. If you forget, other 466 | commands will detect it and remind you to do so if necessary. 467 | ``` 468 | 469 | 470 | 471 | 查看plan,看看会生成什么资源: 472 | 473 | ```bash 474 | $ terraform plan 475 | 476 | Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: 477 | + create 478 | 479 | Terraform will perform the following actions: 480 | 481 | # azurerm_network_interface.main will be created 482 | + resource "azurerm_network_interface" "main" { 483 | + applied_dns_servers = (known after apply) 484 | + dns_servers = (known after apply) 485 | + enable_accelerated_networking = false 486 | + enable_ip_forwarding = false 487 | + id = (known after apply) 488 | + internal_dns_name_label = (known after apply) 489 | + internal_domain_name_suffix = (known after apply) 490 | + location = "westeurope" 491 | + mac_address = (known after apply) 492 | + name = "pkslow-azure-nic" 493 | + private_ip_address = (known after apply) 494 | + private_ip_addresses = (known after apply) 495 | + resource_group_name = "pkslow-azure-resources" 496 | + virtual_machine_id = (known after apply) 497 | 498 | + ip_configuration { 499 | + gateway_load_balancer_frontend_ip_configuration_id = (known after apply) 500 | + name = "testconfiguration1" 501 | + primary = (known after apply) 502 | + private_ip_address = (known after apply) 503 | + private_ip_address_allocation = "Dynamic" 504 | + private_ip_address_version = "IPv4" 505 | + subnet_id = (known after apply) 506 | } 507 | } 508 | 509 | # azurerm_resource_group.example will be created 510 | + resource "azurerm_resource_group" "example" { 511 | + id = (known after apply) 512 | + location = "westeurope" 513 | + name = "pkslow-azure-resources" 514 | } 515 | 516 | # azurerm_subnet.internal will be created 517 | + resource "azurerm_subnet" "internal" { 518 | + address_prefixes = [ 519 | + "10.0.2.0/24", 520 | ] 521 | + enforce_private_link_endpoint_network_policies = (known after apply) 522 | + enforce_private_link_service_network_policies = (known after apply) 523 | + id = (known after apply) 524 | + name = "internal" 525 | + private_endpoint_network_policies_enabled = (known after apply) 526 | + private_link_service_network_policies_enabled = (known after apply) 527 | + resource_group_name = "pkslow-azure-resources" 528 | + virtual_network_name = "pkslow-azure-network" 529 | } 530 | 531 | # azurerm_virtual_machine.main will be created 532 | + resource "azurerm_virtual_machine" "main" { 533 | + availability_set_id = (known after apply) 534 | + delete_data_disks_on_termination = false 535 | + delete_os_disk_on_termination = false 536 | + id = (known after apply) 537 | + license_type = (known after apply) 538 | + location = "westeurope" 539 | + name = "pkslow-azure-vm" 540 | + network_interface_ids = (known after apply) 541 | + resource_group_name = "pkslow-azure-resources" 542 | + tags = { 543 | + "environment" = "staging" 544 | } 545 | + vm_size = "Standard_DS1_v2" 546 | 547 | + identity { 548 | + identity_ids = (known after apply) 549 | + principal_id = (known after apply) 550 | + type = (known after apply) 551 | } 552 | 553 | + os_profile { 554 | + admin_password = (sensitive value) 555 | + admin_username = "larry" 556 | + computer_name = "hostname" 557 | + custom_data = (known after apply) 558 | } 559 | 560 | + os_profile_linux_config { 561 | + disable_password_authentication = false 562 | } 563 | 564 | + storage_data_disk { 565 | + caching = (known after apply) 566 | + create_option = (known after apply) 567 | + disk_size_gb = (known after apply) 568 | + lun = (known after apply) 569 | + managed_disk_id = (known after apply) 570 | + managed_disk_type = (known after apply) 571 | + name = (known after apply) 572 | + vhd_uri = (known after apply) 573 | + write_accelerator_enabled = (known after apply) 574 | } 575 | 576 | + storage_image_reference { 577 | + offer = "0001-com-ubuntu-server-jammy" 578 | + publisher = "Canonical" 579 | + sku = "22_04-lts" 580 | + version = "22.04.202301100" 581 | } 582 | 583 | + storage_os_disk { 584 | + caching = "ReadWrite" 585 | + create_option = "FromImage" 586 | + disk_size_gb = (known after apply) 587 | + managed_disk_id = (known after apply) 588 | + managed_disk_type = "Standard_LRS" 589 | + name = "myosdisk1" 590 | + os_type = (known after apply) 591 | + write_accelerator_enabled = false 592 | } 593 | } 594 | 595 | # azurerm_virtual_network.main will be created 596 | + resource "azurerm_virtual_network" "main" { 597 | + address_space = [ 598 | + "10.0.0.0/16", 599 | ] 600 | + dns_servers = (known after apply) 601 | + guid = (known after apply) 602 | + id = (known after apply) 603 | + location = "westeurope" 604 | + name = "pkslow-azure-network" 605 | + resource_group_name = "pkslow-azure-resources" 606 | + subnet = (known after apply) 607 | } 608 | 609 | Plan: 5 to add, 0 to change, 0 to destroy. 610 | 611 | ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 612 | 613 | Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now. 614 | ``` 615 | 616 | 617 | 618 | 直接apply,创建对应的资源: 619 | 620 | ```bash 621 | $ terraform apply 622 | 623 | Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: 624 | + create 625 | 626 | Terraform will perform the following actions: 627 | 628 | # azurerm_network_interface.main will be created 629 | + resource "azurerm_network_interface" "main" { 630 | + applied_dns_servers = (known after apply) 631 | + dns_servers = (known after apply) 632 | + enable_accelerated_networking = false 633 | + enable_ip_forwarding = false 634 | + id = (known after apply) 635 | + internal_dns_name_label = (known after apply) 636 | + internal_domain_name_suffix = (known after apply) 637 | + location = "westeurope" 638 | + mac_address = (known after apply) 639 | + name = "pkslow-azure-nic" 640 | + private_ip_address = (known after apply) 641 | + private_ip_addresses = (known after apply) 642 | + resource_group_name = "pkslow-azure-resources" 643 | + virtual_machine_id = (known after apply) 644 | 645 | + ip_configuration { 646 | + gateway_load_balancer_frontend_ip_configuration_id = (known after apply) 647 | + name = "testconfiguration1" 648 | + primary = (known after apply) 649 | + private_ip_address = (known after apply) 650 | + private_ip_address_allocation = "Dynamic" 651 | + private_ip_address_version = "IPv4" 652 | + subnet_id = (known after apply) 653 | } 654 | } 655 | 656 | # azurerm_resource_group.example will be created 657 | + resource "azurerm_resource_group" "example" { 658 | + id = (known after apply) 659 | + location = "westeurope" 660 | + name = "pkslow-azure-resources" 661 | } 662 | 663 | # azurerm_subnet.internal will be created 664 | + resource "azurerm_subnet" "internal" { 665 | + address_prefixes = [ 666 | + "10.0.2.0/24", 667 | ] 668 | + enforce_private_link_endpoint_network_policies = (known after apply) 669 | + enforce_private_link_service_network_policies = (known after apply) 670 | + id = (known after apply) 671 | + name = "internal" 672 | + private_endpoint_network_policies_enabled = (known after apply) 673 | + private_link_service_network_policies_enabled = (known after apply) 674 | + resource_group_name = "pkslow-azure-resources" 675 | + virtual_network_name = "pkslow-azure-network" 676 | } 677 | 678 | # azurerm_virtual_machine.main will be created 679 | + resource "azurerm_virtual_machine" "main" { 680 | + availability_set_id = (known after apply) 681 | + delete_data_disks_on_termination = false 682 | + delete_os_disk_on_termination = false 683 | + id = (known after apply) 684 | + license_type = (known after apply) 685 | + location = "westeurope" 686 | + name = "pkslow-azure-vm" 687 | + network_interface_ids = (known after apply) 688 | + resource_group_name = "pkslow-azure-resources" 689 | + tags = { 690 | + "environment" = "staging" 691 | } 692 | + vm_size = "Standard_DS1_v2" 693 | 694 | + identity { 695 | + identity_ids = (known after apply) 696 | + principal_id = (known after apply) 697 | + type = (known after apply) 698 | } 699 | 700 | + os_profile { 701 | + admin_password = (sensitive value) 702 | + admin_username = "larry" 703 | + computer_name = "hostname" 704 | + custom_data = (known after apply) 705 | } 706 | 707 | + os_profile_linux_config { 708 | + disable_password_authentication = false 709 | } 710 | 711 | + storage_data_disk { 712 | + caching = (known after apply) 713 | + create_option = (known after apply) 714 | + disk_size_gb = (known after apply) 715 | + lun = (known after apply) 716 | + managed_disk_id = (known after apply) 717 | + managed_disk_type = (known after apply) 718 | + name = (known after apply) 719 | + vhd_uri = (known after apply) 720 | + write_accelerator_enabled = (known after apply) 721 | } 722 | 723 | + storage_image_reference { 724 | + offer = "0001-com-ubuntu-server-jammy" 725 | + publisher = "Canonical" 726 | + sku = "22_04-lts" 727 | + version = "22.04.202301100" 728 | } 729 | 730 | + storage_os_disk { 731 | + caching = "ReadWrite" 732 | + create_option = "FromImage" 733 | + disk_size_gb = (known after apply) 734 | + managed_disk_id = (known after apply) 735 | + managed_disk_type = "Standard_LRS" 736 | + name = "myosdisk1" 737 | + os_type = (known after apply) 738 | + write_accelerator_enabled = false 739 | } 740 | } 741 | 742 | # azurerm_virtual_network.main will be created 743 | + resource "azurerm_virtual_network" "main" { 744 | + address_space = [ 745 | + "10.0.0.0/16", 746 | ] 747 | + dns_servers = (known after apply) 748 | + guid = (known after apply) 749 | + id = (known after apply) 750 | + location = "westeurope" 751 | + name = "pkslow-azure-network" 752 | + resource_group_name = "pkslow-azure-resources" 753 | + subnet = (known after apply) 754 | } 755 | 756 | Plan: 5 to add, 0 to change, 0 to destroy. 757 | 758 | Do you want to perform these actions? 759 | Terraform will perform the actions described above. 760 | Only 'yes' will be accepted to approve. 761 | 762 | Enter a value: yes 763 | 764 | azurerm_resource_group.example: Creating... 765 | azurerm_resource_group.example: Creation complete after 9s [id=/subscriptions/cd7921d5-9ba9-45db-bfba-1c397fcaaba3/resourceGroups/pkslow-azure-resources] 766 | azurerm_virtual_network.main: Creating... 767 | azurerm_virtual_network.main: Still creating... [10s elapsed] 768 | azurerm_virtual_network.main: Creation complete after 17s [id=/subscriptions/cd7921d5-9ba9-45db-bfba-1c397fcaaba3/resourceGroups/pkslow-azure-resources/providers/Microsoft.Network/virtualNetworks/pkslow-azure-network] 769 | azurerm_subnet.internal: Creating... 770 | azurerm_subnet.internal: Still creating... [10s elapsed] 771 | azurerm_subnet.internal: Creation complete after 11s [id=/subscriptions/cd7921d5-9ba9-45db-bfba-1c397fcaaba3/resourceGroups/pkslow-azure-resources/providers/Microsoft.Network/virtualNetworks/pkslow-azure-network/subnets/internal] 772 | azurerm_network_interface.main: Creating... 773 | azurerm_network_interface.main: Still creating... [10s elapsed] 774 | azurerm_network_interface.main: Creation complete after 10s [id=/subscriptions/cd7921d5-9ba9-45db-bfba-1c397fcaaba3/resourceGroups/pkslow-azure-resources/providers/Microsoft.Network/networkInterfaces/pkslow-azure-nic] 775 | azurerm_virtual_machine.main: Creating... 776 | azurerm_virtual_machine.main: Still creating... [10s elapsed] 777 | azurerm_virtual_machine.main: Still creating... [20s elapsed] 778 | azurerm_virtual_machine.main: Still creating... [30s elapsed] 779 | azurerm_virtual_machine.main: Still creating... [40s elapsed] 780 | azurerm_virtual_machine.main: Still creating... [50s elapsed] 781 | azurerm_virtual_machine.main: Still creating... [1m0s elapsed] 782 | azurerm_virtual_machine.main: Creation complete after 1m0s [id=/subscriptions/cd7921d5-9ba9-45db-bfba-1c397fcaaba3/resourceGroups/pkslow-azure-resources/providers/Microsoft.Compute/virtualMachines/pkslow-azure-vm] 783 | 784 | Apply complete! Resources: 5 added, 0 changed, 0 destroyed. 785 | ``` 786 | 787 | 788 | 789 | 查看所有资源,选择资源组`pkslow-azure-resources`下面的,已经成功创建: 790 | 791 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/azure/terraform-create-resources.png) 792 | 793 | 794 | 795 | 使用完成后,通过下面命令删除: 796 | 797 | ```bash 798 | terraform destroy 799 | ``` 800 | 801 | 802 | 803 | # 部署Azure Kubernetes集群 804 | 805 | ## 通过Auzre CLI部署 806 | 807 | ### 创建资源组 808 | 809 | Azure资源组是用于部署和管理Azure资源的逻辑组。创建资源时,系统会提示你指定一个位置。该位置主要用于: 810 | 811 | (1)资源组元数据的存储位置; 812 | 813 | (2)在创建资源期间未指定另一个区域时,资源在Azure中的运行位置。 814 | 815 | 816 | 817 | 我们通过以下命令来创建资源组: 818 | 819 | ```bash 820 | $ az group create --name pkslow-aks --location eastasia 821 | { 822 | "id": "/subscriptions/cd7921d5-9ba9-45db-bfba-1c397fcaaba3/resourceGroups/pkslow-aks", 823 | "location": "eastasia", 824 | "managedBy": null, 825 | "name": "pkslow-aks", 826 | "properties": { 827 | "provisioningState": "Succeeded" 828 | }, 829 | "tags": null, 830 | "type": "Microsoft.Resources/resourceGroups" 831 | } 832 | ``` 833 | 834 | 835 | 836 | ### 创建AKS 837 | 838 | 通过下面的命令创建AKS: 839 | 840 | ```bash 841 | az aks create -g pkslow-aks -n pkslow --enable-managed-identity --node-count 1 --enable-addons monitoring --enable-msi-auth-for-monitoring --generate-ssh-keys 842 | ``` 843 | 844 | 845 | 846 | 创建完成后会输出很大的Json日志,我们直接来查看一下是否正确生成: 847 | 848 | ```bash 849 | $ az aks list --output table 850 | Name Location ResourceGroup KubernetesVersion CurrentKubernetesVersion ProvisioningState Fqdn 851 | ------ ---------- --------------- ------------------- -------------------------- ------------------- -------------------------------------------------------- 852 | pkslow eastasia pkslow-aks 1.24.6 1.24.6 Succeeded pkslow-pkslow-aks-cd7921-725c7247.hcp.eastasia.azmk8s.io 853 | ``` 854 | 855 | 856 | 857 | ### 连接到AKS 858 | 859 | 需要有`kubectl`命令,没有的就安装一下: 860 | 861 | ```bash 862 | az aks install-cli 863 | ``` 864 | 865 | 866 | 867 | 连接集群需要认证,要获取一下验证配置: 868 | 869 | ```bash 870 | $ az aks get-credentials --resource-group pkslow-aks --name pkslow 871 | Merged "pkslow" as current context in /Users/larry/.kube/config 872 | ``` 873 | 874 | 875 | 876 | 成功后就可以连接并操作了: 877 | 878 | ```bash 879 | $ kubectl get node 880 | NAME STATUS ROLES AGE VERSION 881 | aks-nodepool1-29201873-vmss000000 Ready agent 8m45s v1.24.6 882 | 883 | 884 | $ kubectl get ns 885 | NAME STATUS AGE 886 | default Active 9m33s 887 | kube-node-lease Active 9m35s 888 | kube-public Active 9m35s 889 | kube-system Active 9m35s 890 | 891 | 892 | $ kubectl get pod -n kube-system 893 | NAME READY STATUS RESTARTS AGE 894 | ama-logs-lhlkb 3/3 Running 0 9m8s 895 | ama-logs-rs-6cf9546595-rdmh9 2/2 Running 0 9m26s 896 | azure-ip-masq-agent-nppvd 1/1 Running 0 9m8s 897 | cloud-node-manager-bd4c2 1/1 Running 0 9m8s 898 | coredns-59b6bf8b4f-lrzpp 1/1 Running 0 9m26s 899 | coredns-59b6bf8b4f-zbbkm 1/1 Running 0 7m56s 900 | coredns-autoscaler-5655d66f64-5946c 1/1 Running 0 9m26s 901 | csi-azuredisk-node-9rpvd 3/3 Running 0 9m8s 902 | csi-azurefile-node-hvxhc 3/3 Running 0 9m8s 903 | konnectivity-agent-95ff8bbd-fwkds 1/1 Running 0 9m26s 904 | konnectivity-agent-95ff8bbd-qg9vx 1/1 Running 0 9m26s 905 | kube-proxy-c5crz 1/1 Running 0 9m8s 906 | metrics-server-7dd74d8758-ms8h9 2/2 Running 0 7m50s 907 | metrics-server-7dd74d8758-nxq9t 2/2 Running 0 7m50s 908 | ``` 909 | 910 | 911 | 912 | ### 部署测试应用 913 | 914 | 为了方便,我们直接使用官网的示例来测试一下。创建文件`azure-vote.yaml`,内容如下: 915 | 916 | ```yaml 917 | apiVersion: apps/v1 918 | kind: Deployment 919 | metadata: 920 | name: azure-vote-back 921 | spec: 922 | replicas: 1 923 | selector: 924 | matchLabels: 925 | app: azure-vote-back 926 | template: 927 | metadata: 928 | labels: 929 | app: azure-vote-back 930 | spec: 931 | nodeSelector: 932 | "kubernetes.io/os": linux 933 | containers: 934 | - name: azure-vote-back 935 | image: mcr.microsoft.com/oss/bitnami/redis:6.0.8 936 | env: 937 | - name: ALLOW_EMPTY_PASSWORD 938 | value: "yes" 939 | resources: 940 | requests: 941 | cpu: 100m 942 | memory: 128Mi 943 | limits: 944 | cpu: 250m 945 | memory: 256Mi 946 | ports: 947 | - containerPort: 6379 948 | name: redis 949 | --- 950 | apiVersion: v1 951 | kind: Service 952 | metadata: 953 | name: azure-vote-back 954 | spec: 955 | ports: 956 | - port: 6379 957 | selector: 958 | app: azure-vote-back 959 | --- 960 | apiVersion: apps/v1 961 | kind: Deployment 962 | metadata: 963 | name: azure-vote-front 964 | spec: 965 | replicas: 1 966 | selector: 967 | matchLabels: 968 | app: azure-vote-front 969 | template: 970 | metadata: 971 | labels: 972 | app: azure-vote-front 973 | spec: 974 | nodeSelector: 975 | "kubernetes.io/os": linux 976 | containers: 977 | - name: azure-vote-front 978 | image: mcr.microsoft.com/azuredocs/azure-vote-front:v1 979 | resources: 980 | requests: 981 | cpu: 100m 982 | memory: 128Mi 983 | limits: 984 | cpu: 250m 985 | memory: 256Mi 986 | ports: 987 | - containerPort: 80 988 | env: 989 | - name: REDIS 990 | value: "azure-vote-back" 991 | --- 992 | apiVersion: v1 993 | kind: Service 994 | metadata: 995 | name: azure-vote-front 996 | spec: 997 | type: LoadBalancer 998 | ports: 999 | - port: 80 1000 | selector: 1001 | app: azure-vote-front 1002 | ``` 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | 然后执行以下命令: 1009 | 1010 | ```bash 1011 | $ kubectl apply -f azure-vote.yaml 1012 | deployment.apps/azure-vote-back created 1013 | service/azure-vote-back created 1014 | deployment.apps/azure-vote-front created 1015 | service/azure-vote-front created 1016 | ``` 1017 | 1018 | 1019 | 1020 | 成功后查看对应资源: 1021 | 1022 | ```bash 1023 | $ kubectl get svc 1024 | NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE 1025 | azure-vote-back ClusterIP 10.0.156.161 6379/TCP 112s 1026 | azure-vote-front LoadBalancer 10.0.29.217 20.239.124.1 80:30289/TCP 112s 1027 | kubernetes ClusterIP 10.0.0.1 443/TCP 21m 1028 | 1029 | $ kubectl get deployment 1030 | NAME READY UP-TO-DATE AVAILABLE AGE 1031 | azure-vote-back 1/1 1 1 2m1s 1032 | azure-vote-front 1/1 1 1 2m1s 1033 | 1034 | $ kubectl get pod 1035 | NAME READY STATUS RESTARTS AGE 1036 | azure-vote-back-7cd69cc96f-gqm7r 1/1 Running 0 2m7s 1037 | azure-vote-front-7c95676c68-jtkqz 1/1 Running 0 2m7s 1038 | ``` 1039 | 1040 | 已经成功创建。 1041 | 1042 | 看front那有external IP,通过它直接在浏览器访问如下: 1043 | 1044 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/azure/aks-cli.front-page.png) 1045 | 1046 | 1047 | 1048 | 应用已经成功部署并访问了。 1049 | 1050 | 1051 | 1052 | ### 删除资源组 1053 | 1054 | 如果完成测试,不再使用,可以整个资源组一起删除: 1055 | 1056 | ```bash 1057 | az group delete --name pkslow-aks --yes --no-wait 1058 | ``` 1059 | 1060 | 1061 | 1062 | ## 通过Terraform部署 1063 | 1064 | ### 配置插件和版本 1065 | 1066 | ```hcl 1067 | terraform { 1068 | required_version = ">= 1.1.3" 1069 | required_providers { 1070 | 1071 | azurerm = { 1072 | source = "hashicorp/azurerm" 1073 | version = "3.38.0" 1074 | } 1075 | 1076 | random = { 1077 | source = "hashicorp/random" 1078 | version = "= 3.1.0" 1079 | } 1080 | } 1081 | } 1082 | ``` 1083 | 1084 | 1085 | 1086 | ### 变量设置 1087 | 1088 | 给`Terraform`设置一些要用到的变量: 1089 | 1090 | ```hcl 1091 | variable "agent_count" { 1092 | default = 1 1093 | } 1094 | 1095 | # The following two variable declarations are placeholder references. 1096 | # Set the values for these variable in terraform.tfvars 1097 | variable "aks_service_principal_app_id" { 1098 | default = "" 1099 | } 1100 | 1101 | variable "aks_service_principal_client_secret" { 1102 | default = "" 1103 | } 1104 | 1105 | variable "cluster_name" { 1106 | default = "pkslow-k8s" 1107 | } 1108 | 1109 | variable "dns_prefix" { 1110 | default = "pkslow" 1111 | } 1112 | 1113 | # Refer to https://azure.microsoft.com/global-infrastructure/services/?products=monitor for available Log Analytics regions. 1114 | variable "log_analytics_workspace_location" { 1115 | default = "eastus" 1116 | } 1117 | 1118 | variable "log_analytics_workspace_name" { 1119 | default = "testLogAnalyticsWorkspaceName" 1120 | } 1121 | 1122 | # Refer to https://azure.microsoft.com/pricing/details/monitor/ for Log Analytics pricing 1123 | variable "log_analytics_workspace_sku" { 1124 | default = "PerGB2018" 1125 | } 1126 | 1127 | variable "resource_group_location" { 1128 | default = "eastus" 1129 | description = "Location of the resource group." 1130 | } 1131 | 1132 | variable "resource_group_name_prefix" { 1133 | default = "rg" 1134 | description = "Prefix of the resource group name that's combined with a random ID so name is unique in your Azure subscription." 1135 | } 1136 | 1137 | variable "ssh_public_key" { 1138 | default = "~/.ssh/id_rsa.pub" 1139 | } 1140 | ``` 1141 | 1142 | 1143 | 1144 | `agent_count`应该设置合理,这里设成1是因为我的账号是免费的,有限制。 1145 | 1146 | 1147 | 1148 | ### 输出结果 1149 | 1150 | 当Terraform执行完,会有一些结果,我们可以把一些值输出以便使用: 1151 | 1152 | ```hcl 1153 | output "client_certificate" { 1154 | value = azurerm_kubernetes_cluster.k8s.kube_config[0].client_certificate 1155 | sensitive = true 1156 | } 1157 | 1158 | output "client_key" { 1159 | value = azurerm_kubernetes_cluster.k8s.kube_config[0].client_key 1160 | sensitive = true 1161 | } 1162 | 1163 | output "cluster_ca_certificate" { 1164 | value = azurerm_kubernetes_cluster.k8s.kube_config[0].cluster_ca_certificate 1165 | sensitive = true 1166 | } 1167 | 1168 | output "cluster_password" { 1169 | value = azurerm_kubernetes_cluster.k8s.kube_config[0].password 1170 | sensitive = true 1171 | } 1172 | 1173 | output "cluster_username" { 1174 | value = azurerm_kubernetes_cluster.k8s.kube_config[0].username 1175 | sensitive = true 1176 | } 1177 | 1178 | output "host" { 1179 | value = azurerm_kubernetes_cluster.k8s.kube_config[0].host 1180 | sensitive = true 1181 | } 1182 | 1183 | output "kube_config" { 1184 | value = azurerm_kubernetes_cluster.k8s.kube_config_raw 1185 | sensitive = true 1186 | } 1187 | 1188 | output "resource_group_name" { 1189 | value = azurerm_resource_group.rg.name 1190 | } 1191 | ``` 1192 | 1193 | 1194 | 1195 | ### main.tf创建AKS 1196 | 1197 | 通过`azurerm_kubernetes_cluster`创建AKS: 1198 | 1199 | ```hcl 1200 | provider "azurerm" { 1201 | features {} 1202 | } 1203 | 1204 | # Generate random resource group name 1205 | resource "random_pet" "rg_name" { 1206 | prefix = var.resource_group_name_prefix 1207 | } 1208 | 1209 | resource "azurerm_resource_group" "rg" { 1210 | location = var.resource_group_location 1211 | name = random_pet.rg_name.id 1212 | } 1213 | 1214 | resource "random_id" "log_analytics_workspace_name_suffix" { 1215 | byte_length = 8 1216 | } 1217 | 1218 | resource "azurerm_log_analytics_workspace" "test" { 1219 | location = var.log_analytics_workspace_location 1220 | # The WorkSpace name has to be unique across the whole of azure; 1221 | # not just the current subscription/tenant. 1222 | name = "${var.log_analytics_workspace_name}-${random_id.log_analytics_workspace_name_suffix.dec}" 1223 | resource_group_name = azurerm_resource_group.rg.name 1224 | sku = var.log_analytics_workspace_sku 1225 | } 1226 | 1227 | resource "azurerm_log_analytics_solution" "test" { 1228 | location = azurerm_log_analytics_workspace.test.location 1229 | resource_group_name = azurerm_resource_group.rg.name 1230 | solution_name = "ContainerInsights" 1231 | workspace_name = azurerm_log_analytics_workspace.test.name 1232 | workspace_resource_id = azurerm_log_analytics_workspace.test.id 1233 | 1234 | plan { 1235 | product = "OMSGallery/ContainerInsights" 1236 | publisher = "Microsoft" 1237 | } 1238 | } 1239 | 1240 | resource "azurerm_kubernetes_cluster" "k8s" { 1241 | location = azurerm_resource_group.rg.location 1242 | name = var.cluster_name 1243 | resource_group_name = azurerm_resource_group.rg.name 1244 | dns_prefix = var.dns_prefix 1245 | tags = { 1246 | Environment = "Development" 1247 | } 1248 | 1249 | default_node_pool { 1250 | name = "agentpool" 1251 | vm_size = "Standard_D2_v2" 1252 | node_count = var.agent_count 1253 | } 1254 | linux_profile { 1255 | admin_username = "ubuntu" 1256 | 1257 | ssh_key { 1258 | key_data = file(var.ssh_public_key) 1259 | } 1260 | } 1261 | network_profile { 1262 | network_plugin = "kubenet" 1263 | load_balancer_sku = "standard" 1264 | } 1265 | service_principal { 1266 | client_id = var.aks_service_principal_app_id 1267 | client_secret = var.aks_service_principal_client_secret 1268 | } 1269 | } 1270 | ``` 1271 | 1272 | 1273 | 1274 | ### 执行 1275 | 1276 | 准备好文件后,先初始化,下载插件: 1277 | 1278 | ```bash 1279 | $ terraform init 1280 | 1281 | Initializing the backend... 1282 | 1283 | Initializing provider plugins... 1284 | - Finding hashicorp/random versions matching "3.1.0"... 1285 | - Finding hashicorp/azurerm versions matching "3.38.0"... 1286 | - Installing hashicorp/random v3.1.0... 1287 | - Installed hashicorp/random v3.1.0 (unauthenticated) 1288 | - Installing hashicorp/azurerm v3.38.0... 1289 | - Installed hashicorp/azurerm v3.38.0 (signed by HashiCorp) 1290 | 1291 | Terraform has created a lock file .terraform.lock.hcl to record the provider 1292 | selections it made above. Include this file in your version control repository 1293 | so that Terraform can guarantee to make the same selections by default when 1294 | you run "terraform init" in the future. 1295 | 1296 | Terraform has been successfully initialized! 1297 | 1298 | You may now begin working with Terraform. Try running "terraform plan" to see 1299 | any changes that are required for your infrastructure. All Terraform commands 1300 | should now work. 1301 | 1302 | If you ever set or change modules or backend configuration for Terraform, 1303 | rerun this command to reinitialize your working directory. If you forget, other 1304 | commands will detect it and remind you to do so if necessary. 1305 | ``` 1306 | 1307 | 1308 | 1309 | 查看Terraform计划,知道将要生成多少资源: 1310 | 1311 | ```bash 1312 | $ terraform plan -out main.tfplan -var="aks_service_principal_app_id=$ARM_CLIENT_ID" -var="aks_service_principal_client_secret=$ARM_CLIENT_SECRET" 1313 | ``` 1314 | 1315 | 1316 | 1317 | 没有问题则执行变更: 1318 | 1319 | ```bash 1320 | $ terraform apply main.tfplan 1321 | Outputs: 1322 | 1323 | client_certificate = 1324 | client_key = 1325 | cluster_ca_certificate = 1326 | cluster_password = 1327 | cluster_username = 1328 | host = 1329 | kube_config = 1330 | resource_group_name = "rg-harmless-tomcat" 1331 | ``` 1332 | 1333 | 1334 | 1335 | ### 连接AKS 1336 | 1337 | 把kube_config输出,然后设置环境变量就可以通过kubectl连接了: 1338 | 1339 | ```bash 1340 | $ echo "$(terraform output kube_config)" > ./azurek8s 1341 | 1342 | $ export KUBECONFIG=./azurek8s 1343 | 1344 | $ kubectl get nodes 1345 | NAME STATUS ROLES AGE VERSION 1346 | aks-agentpool-45159290-vmss000000 Ready agent 9m20s v1.24.6 1347 | ``` 1348 | 1349 | 如果有问题,可以查看azurek8s文件是否正常。 1350 | 1351 | 1352 | 1353 | # 创建PostgreSQL 1354 | 1355 | 1356 | 1357 | ### 通过Azure CLI创建Single Server 1358 | 1359 | ### 创建资源组和数据库 1360 | 1361 | 先创建资源组: 1362 | 1363 | ```bash 1364 | az group create --name pkslow-sql --location eastasia --tag create-postgresql-server-and-firewall-rule 1365 | ``` 1366 | 1367 | 1368 | 1369 | 然后创建数据库: 1370 | 1371 | ```bash 1372 | $ az postgres server create \ 1373 | > --name pkslow-pg \ 1374 | > --resource-group pkslow-sql \ 1375 | > --location eastasia \ 1376 | > --admin-user pguser \ 1377 | > --admin-password 'Pa$$word' \ 1378 | > --sku-name GP_Gen5_2 1379 | 1380 | 1381 | Checking the existence of the resource group 'pkslow-sql'... 1382 | Resource group 'pkslow-sql' exists ? : True 1383 | Creating postgres Server 'pkslow-pg' in group 'pkslow-sql'... 1384 | Your server 'pkslow-pg' is using sku 'GP_Gen5_2' (Paid Tier). Please refer to https://aka.ms/postgres-pricing for pricing details 1385 | Make a note of your password. If you forget, you would have to reset your password with 'az postgres server update -n pkslow-pg -g pkslow-sql -p '. 1386 | { 1387 | "additionalProperties": {}, 1388 | "administratorLogin": "pguser", 1389 | "byokEnforcement": "Disabled", 1390 | "connectionString": "postgres://pguser%40pkslow-pg:Pa$$word@pkslow-pg.postgres.database.azure.com/postgres?sslmode=require", 1391 | "earliestRestoreDate": "2023-01-15T03:24:18.440000+00:00", 1392 | "fullyQualifiedDomainName": "pkslow-pg.postgres.database.azure.com", 1393 | "id": "/subscriptions/cd7921d5-9ba9-45db-bfba-1c397fcaaba3/resourceGroups/pkslow-sql/providers/Microsoft.DBforPostgreSQL/servers/pkslow-pg", 1394 | "identity": null, 1395 | "infrastructureEncryption": "Disabled", 1396 | "location": "eastasia", 1397 | "masterServerId": "", 1398 | "minimalTlsVersion": "TLSEnforcementDisabled", 1399 | "name": "pkslow-pg", 1400 | "password": "Pa$$word", 1401 | "privateEndpointConnections": [], 1402 | "publicNetworkAccess": "Enabled", 1403 | "replicaCapacity": 5, 1404 | "replicationRole": "None", 1405 | "resourceGroup": "pkslow-sql", 1406 | "sku": { 1407 | "additionalProperties": {}, 1408 | "capacity": 2, 1409 | "family": "Gen5", 1410 | "name": "GP_Gen5_2", 1411 | "size": null, 1412 | "tier": "GeneralPurpose" 1413 | }, 1414 | "sslEnforcement": "Enabled", 1415 | "storageProfile": { 1416 | "additionalProperties": {}, 1417 | "backupRetentionDays": 7, 1418 | "geoRedundantBackup": "Disabled", 1419 | "storageAutogrow": "Enabled", 1420 | "storageMb": 5120 1421 | }, 1422 | "tags": null, 1423 | "type": "Microsoft.DBforPostgreSQL/servers", 1424 | "userVisibleState": "Ready", 1425 | "version": "11" 1426 | } 1427 | ``` 1428 | 1429 | 创建成功后,会打印很多有用的信息,如连接信息。 1430 | 1431 | 1432 | 1433 | 也可以在以后查看: 1434 | 1435 | ```bash 1436 | az postgres server show --resource-group pkslow-sql --name pkslow-pg 1437 | ``` 1438 | 1439 | 1440 | 1441 | ### 禁用SSL 1442 | 1443 | 创建完成后还可以更新一些配置,如我们禁用SSL: 1444 | 1445 | ```bash 1446 | az postgres server update --resource-group pkslow-sql --name pkslow-pg --ssl-enforcement Disabled 1447 | ``` 1448 | 1449 | > 生产环境不要禁用SSL。 1450 | 1451 | 1452 | 1453 | ### 添加防火墙 1454 | 1455 | 需要把客户端IP添加到Firewall,不然会连接失败。 1456 | 1457 | ```bash 1458 | az postgres server firewall-rule create \ 1459 | --resource-group pkslow-sql \ 1460 | --server pkslow-pg \ 1461 | --name AllowIps \ 1462 | --start-ip-address '0.0.0.0' \ 1463 | --end-ip-address '255.255.255.255' 1464 | ``` 1465 | 1466 | 1467 | 1468 | ### 测试连接 1469 | 1470 | 配置连接如下,注意用户名不只是`pguser`: 1471 | 1472 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/azure/postgresql.cli.connected.png) 1473 | 1474 | 1475 | 1476 | ### 删除资源 1477 | 1478 | 如果不需要再使用,就删除资源: 1479 | 1480 | ```bash 1481 | az group delete --name pkslow-sql 1482 | ``` 1483 | 1484 | 1485 | 1486 | ## 通过Terraform创建Flexible Server 1487 | 1488 | 1489 | 1490 | ### 插件与版本 1491 | 1492 | ```hcl 1493 | terraform { 1494 | required_version = ">= 1.1.3" 1495 | required_providers { 1496 | 1497 | azurerm = { 1498 | source = "hashicorp/azurerm" 1499 | version = "3.38.0" 1500 | } 1501 | } 1502 | } 1503 | 1504 | provider "azurerm" { 1505 | features {} 1506 | } 1507 | ``` 1508 | 1509 | 1510 | 1511 | ### 变量设置 1512 | 1513 | ```hcl 1514 | variable "name_prefix" { 1515 | default = "pkslow-pg-fs" 1516 | description = "Prefix of the resource name." 1517 | } 1518 | 1519 | variable "location" { 1520 | default = "eastus" 1521 | description = "Location of the resource." 1522 | } 1523 | ``` 1524 | 1525 | 1526 | 1527 | ### main.tf创建 1528 | 1529 | ```hcl 1530 | resource "random_pet" "rg-name" { 1531 | prefix = var.name_prefix 1532 | } 1533 | 1534 | resource "azurerm_resource_group" "default" { 1535 | name = random_pet.rg-name.id 1536 | location = var.location 1537 | } 1538 | 1539 | resource "azurerm_virtual_network" "default" { 1540 | name = "${var.name_prefix}-vnet" 1541 | location = azurerm_resource_group.default.location 1542 | resource_group_name = azurerm_resource_group.default.name 1543 | address_space = ["10.0.0.0/16"] 1544 | } 1545 | 1546 | resource "azurerm_network_security_group" "default" { 1547 | name = "${var.name_prefix}-nsg" 1548 | location = azurerm_resource_group.default.location 1549 | resource_group_name = azurerm_resource_group.default.name 1550 | 1551 | security_rule { 1552 | name = "test123" 1553 | priority = 100 1554 | direction = "Inbound" 1555 | access = "Allow" 1556 | protocol = "Tcp" 1557 | source_port_range = "*" 1558 | destination_port_range = "*" 1559 | source_address_prefix = "*" 1560 | destination_address_prefix = "*" 1561 | } 1562 | } 1563 | 1564 | resource "azurerm_subnet" "default" { 1565 | name = "${var.name_prefix}-subnet" 1566 | virtual_network_name = azurerm_virtual_network.default.name 1567 | resource_group_name = azurerm_resource_group.default.name 1568 | address_prefixes = ["10.0.2.0/24"] 1569 | service_endpoints = ["Microsoft.Storage"] 1570 | 1571 | delegation { 1572 | name = "fs" 1573 | 1574 | service_delegation { 1575 | name = "Microsoft.DBforPostgreSQL/flexibleServers" 1576 | 1577 | actions = [ 1578 | "Microsoft.Network/virtualNetworks/subnets/join/action", 1579 | ] 1580 | } 1581 | } 1582 | } 1583 | 1584 | resource "azurerm_subnet_network_security_group_association" "default" { 1585 | subnet_id = azurerm_subnet.default.id 1586 | network_security_group_id = azurerm_network_security_group.default.id 1587 | } 1588 | 1589 | resource "azurerm_private_dns_zone" "default" { 1590 | name = "${var.name_prefix}-pdz.postgres.database.azure.com" 1591 | resource_group_name = azurerm_resource_group.default.name 1592 | 1593 | depends_on = [azurerm_subnet_network_security_group_association.default] 1594 | } 1595 | 1596 | resource "azurerm_private_dns_zone_virtual_network_link" "default" { 1597 | name = "${var.name_prefix}-pdzvnetlink.com" 1598 | private_dns_zone_name = azurerm_private_dns_zone.default.name 1599 | virtual_network_id = azurerm_virtual_network.default.id 1600 | resource_group_name = azurerm_resource_group.default.name 1601 | } 1602 | 1603 | resource "azurerm_postgresql_flexible_server" "default" { 1604 | name = "${var.name_prefix}-server" 1605 | resource_group_name = azurerm_resource_group.default.name 1606 | location = azurerm_resource_group.default.location 1607 | version = "13" 1608 | delegated_subnet_id = azurerm_subnet.default.id 1609 | private_dns_zone_id = azurerm_private_dns_zone.default.id 1610 | administrator_login = "pguser" 1611 | administrator_password = "QAZwsx123" 1612 | zone = "1" 1613 | storage_mb = 32768 1614 | sku_name = "GP_Standard_D2s_v3" 1615 | backup_retention_days = 7 1616 | 1617 | depends_on = [azurerm_private_dns_zone_virtual_network_link.default] 1618 | } 1619 | ``` 1620 | 1621 | 1622 | 1623 | 准备文件:pg-fs-db.tf 1624 | 1625 | ```hcl 1626 | resource "azurerm_postgresql_flexible_server_database" "default" { 1627 | name = "${var.name_prefix}-db" 1628 | server_id = azurerm_postgresql_flexible_server.default.id 1629 | collation = "en_US.UTF8" 1630 | charset = "UTF8" 1631 | } 1632 | ``` 1633 | 1634 | 1635 | 1636 | ### 输出结果 1637 | 1638 | ```hcl 1639 | output "resource_group_name" { 1640 | value = azurerm_resource_group.default.name 1641 | } 1642 | 1643 | output "azurerm_postgresql_flexible_server" { 1644 | value = azurerm_postgresql_flexible_server.default.name 1645 | } 1646 | 1647 | output "postgresql_flexible_server_database_name" { 1648 | value = azurerm_postgresql_flexible_server_database.default.name 1649 | } 1650 | ``` 1651 | 1652 | 1653 | 1654 | ### 执行 1655 | 1656 | 准备好hcl文件后,执行如下: 1657 | 1658 | ```bash 1659 | $ terraform init 1660 | 1661 | $ terraform plan -out main.tfplan 1662 | 1663 | $ terraform apply main.tfplan 1664 | Apply complete! Resources: 10 added, 0 changed, 0 destroyed. 1665 | 1666 | Outputs: 1667 | azurerm_postgresql_flexible_server = "pkslow-pg-fs-server" 1668 | postgresql_flexible_server_database_name = "pkslow-pg-fs-db" 1669 | resource_group_name = "pkslow-pg-fs-delicate-honeybee" 1670 | ``` 1671 | 1672 | 1673 | 1674 | 创建成功后,可以查看: 1675 | 1676 | ```bash 1677 | $ az postgres flexible-server list --output table 1678 | Name Resource Group Location Version Storage Size(GiB) Tier SKU State HA State Availability zone 1679 | ------------------- ------------------------------ ---------- --------- ------------------- -------------- --------------- ------- ---------- ------------------- 1680 | pkslow-pg-fs-server pkslow-pg-fs-delicate-honeybee East US 13 32 GeneralPurpose Standard_D2s_v3 Ready NotEnabled 1 1681 | ``` 1682 | 1683 | 1684 | 1685 | 当然,在Portal上看也是可以的: 1686 | 1687 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/azure/postgresql.terraform.portal.png) 1688 | 1689 | 1690 | 1691 | ### 删除 1692 | 1693 | 不需要了可以执行删除: 1694 | 1695 | ```bash 1696 | terraform destroy 1697 | ``` 1698 | 1699 | 1700 | 1701 | # 在Azure云存储上管理Terraform状态 1702 | 1703 | 默认Terraform的状态是保存在本地的,为了安全和协作,在生产环境中一般要保存在云上。 1704 | 1705 | 1706 | 1707 | ## 创建Azure Storage 1708 | 1709 | 我们创建Storage来存储Terraform状态。按下面一步步执行即可: 1710 | 1711 | ```bash 1712 | RESOURCE_GROUP_NAME=pkslow-tstate-rg 1713 | STORAGE_ACCOUNT_NAME=pkslowtfstate 1714 | CONTAINER_NAME=tfstate 1715 | 1716 | # Create resource group 1717 | az group create --name $RESOURCE_GROUP_NAME --location "West Europe" 1718 | 1719 | # Create storage account 1720 | az storage account create --resource-group $RESOURCE_GROUP_NAME --name $STORAGE_ACCOUNT_NAME --sku Standard_LRS --encryption-services blob 1721 | 1722 | # Get storage account key 1723 | ACCOUNT_KEY=$(az storage account keys list --resource-group $RESOURCE_GROUP_NAME --account-name $STORAGE_ACCOUNT_NAME --query [0].value -o tsv) 1724 | 1725 | # Create blob container 1726 | az storage container create --name $CONTAINER_NAME --account-name $STORAGE_ACCOUNT_NAME --account-key $ACCOUNT_KEY 1727 | 1728 | echo "storage_account_name: $STORAGE_ACCOUNT_NAME" 1729 | echo "container_name: $CONTAINER_NAME" 1730 | echo "access_key: $ACCOUNT_KEY" 1731 | ``` 1732 | 1733 | 1734 | 1735 | ## Terraform backend 1736 | 1737 | 创建完Storage后,我们需要在Terraform中配置使用: 1738 | 1739 | ```hcl 1740 | terraform { 1741 | required_version = ">= 1.1.3" 1742 | required_providers { 1743 | 1744 | azurerm = { 1745 | source = "hashicorp/azurerm" 1746 | version = "3.38.0" 1747 | } 1748 | local = { 1749 | source = "hashicorp/local" 1750 | version = "= 2.1.0" 1751 | } 1752 | } 1753 | 1754 | backend "azurerm" { 1755 | resource_group_name = "pkslow-tstate-rg" 1756 | storage_account_name = "pkslowtfstate" 1757 | container_name = "tfstate" 1758 | key = "pkslow.tfstate" 1759 | } 1760 | } 1761 | 1762 | provider "azurerm" { 1763 | features {} 1764 | } 1765 | 1766 | resource "local_file" "test-file" { 1767 | content = "https://www.pkslow.com" 1768 | filename = "${path.root}/terraform-guides-by-pkslow.txt" 1769 | } 1770 | ``` 1771 | 1772 | 1773 | 1774 | 主要代码是这块: 1775 | 1776 | ```hcl 1777 | backend "azurerm" { 1778 | resource_group_name = "pkslow-tstate-rg" 1779 | storage_account_name = "pkslowtfstate" 1780 | container_name = "tfstate" 1781 | key = "pkslow.tfstate" 1782 | } 1783 | ``` 1784 | 1785 | 这里前三个变量的值都是前面创建Storage的时候指定的。 1786 | 1787 | 1788 | 1789 | ## 执行Terraform 1790 | 1791 | 初始化: 1792 | 1793 | ```bash 1794 | $ terraform init 1795 | 1796 | Initializing the backend... 1797 | 1798 | Initializing provider plugins... 1799 | - Finding hashicorp/local versions matching "2.1.0"... 1800 | - Finding hashicorp/azurerm versions matching "3.38.0"... 1801 | - Installing hashicorp/local v2.1.0... 1802 | - Installed hashicorp/local v2.1.0 (unauthenticated) 1803 | - Installing hashicorp/azurerm v3.38.0... 1804 | - Installed hashicorp/azurerm v3.38.0 (signed by HashiCorp) 1805 | 1806 | Terraform has created a lock file .terraform.lock.hcl to record the provider 1807 | selections it made above. Include this file in your version control repository 1808 | so that Terraform can guarantee to make the same selections by default when 1809 | you run "terraform init" in the future. 1810 | 1811 | Terraform has been successfully initialized! 1812 | 1813 | You may now begin working with Terraform. Try running "terraform plan" to see 1814 | any changes that are required for your infrastructure. All Terraform commands 1815 | should now work. 1816 | 1817 | If you ever set or change modules or backend configuration for Terraform, 1818 | rerun this command to reinitialize your working directory. If you forget, other 1819 | commands will detect it and remind you to do so if necessary. 1820 | ``` 1821 | 1822 | 看日志就会初始化backend。 1823 | 1824 | 1825 | 1826 | 执行apply: 1827 | 1828 | ```bash 1829 | $ terraform apply -auto-approve 1830 | Acquiring state lock. This may take a few moments... 1831 | 1832 | Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: 1833 | + create 1834 | 1835 | Terraform will perform the following actions: 1836 | 1837 | # local_file.test-file will be created 1838 | + resource "local_file" "test-file" { 1839 | + content = "https://www.pkslow.com" 1840 | + directory_permission = "0777" 1841 | + file_permission = "0777" 1842 | + filename = "./terraform-guides-by-pkslow.txt" 1843 | + id = (known after apply) 1844 | } 1845 | 1846 | Plan: 1 to add, 0 to change, 0 to destroy. 1847 | local_file.test-file: Creating... 1848 | local_file.test-file: Creation complete after 0s [id=6db7ad1bbf57df0c859cd5fc62ff5408515b5fc1] 1849 | Releasing state lock. This may take a few moments... 1850 | 1851 | Apply complete! Resources: 1 added, 0 changed, 0 destroyed. 1852 | ``` 1853 | 1854 | 1855 | 1856 | 然后我们去查看Azure Storage,就可以发现已经生成一个Terraform状态文件: 1857 | 1858 | ![](https://pkslow.oss-cn-shenzhen.aliyuncs.com/images/other/terraform-101/pictures/public-cloud/azure/state.azure-storage.png) 1859 | 1860 | 1861 | 1862 | 1863 | 1864 | 如果不再使用,记得删除资源。 1865 | 1866 | 1867 | 1868 | --- 1869 | 1870 | [Creating a service principal](https://learn.microsoft.com/en-us/azure/purview/create-service-principal-azure) 1871 | 1872 | [Use the portal to create an Azure AD application and service principal that can access resources](https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal) 1873 | 1874 | [Sign in with Azure CLI](https://learn.microsoft.com/en-us/cli/azure/authenticate-azure-cli) 1875 | 1876 | [az vm命令](https://learn.microsoft.com/en-us/cli/azure/vm?view=azure-cli-latest#az-vm-list) 1877 | 1878 | [azure-cli输出格式](https://learn.microsoft.com/en-us/cli/azure/format-output-azure-cli) 1879 | 1880 | [Azure关联订阅](https://learn.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-how-subscriptions-associated-directory) 1881 | 1882 | [azurerm_virtual_machine](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine) 1883 | 1884 | [Create an Azure VM cluster with Terraform and HCL](https://learn.microsoft.com/en-us/azure/developer/terraform/create-vm-cluster-with-infrastructure) 1885 | 1886 | [Install Terraform on Windows with Bash](https://learn.microsoft.com/en-us/azure/developer/terraform/get-started-windows-bash?tabs=bash) 1887 | 1888 | [快速入门:使用Azure CLI部署Azure Kubernetes服务集群](https://learn.microsoft.com/zh-cn/azure/aks/learn/quick-kubernetes-deploy-cli) 1889 | 1890 | [Quickstart: Create a Kubernetes cluster with Azure Kubernetes Service using Terraform](https://learn.microsoft.com/en-us/azure/developer/terraform/create-k8s-cluster-with-tf-and-aks) 1891 | 1892 | [Quickstart: Create an Azure Database for PostgreSQL server by using the Azure CLI](https://learn.microsoft.com/en-us/azure/postgresql/single-server/quickstart-create-server-database-azure-cli) 1893 | 1894 | [Deploy a PostgreSQL Flexible Server Database using Terraform](https://learn.microsoft.com/en-us/azure/developer/terraform/deploy-postgresql-flexible-server-database?tabs=azure-cli) 1895 | 1896 | [PostgreSQL Flexible Server error: Operations on a server group in dropping state are not allowed](https://github.com/hashicorp/terraform-provider-azurerm/issues/16622) 1897 | 1898 | [How to Create an Azure Remote Backend for Terraform](https://gmusumeci.medium.com/how-to-create-an-azure-remote-backend-for-terraform-67cce5da1520) 1899 | 1900 | --------------------------------------------------------------------------------