Terraform template¶
Backend submodule¶
Após Configurar o Azure para Terraform,
vamos configurar o backend do Terraform, que será o local onde ele irá salvar todos os "Terraform States".
Podemos configurar diferentes tipos de backend, tal como documentado aqui: https://developer.hashicorp.com/terraform/language/backend ,
é possivel usar mais do que um provider em simultaneo.
De momento vamos configurar apenas backend no Microsoft Azure, mas como mais tarde poderemos querer alterar este provider,
foi criado o projecto Terraform_Backend no gitlab onde apenas está configurado o backend,
e todos os projectos de Terraform devem importar este projecto como um git submodule,
isso facilitará uma alteração futura, apenas actualizando esse repositório,
e executando git submodule update --init && git submodule update --remote em todos os restantes repositórios para actualizar o git submodule.
Este projecto apenas contem 3 ficheiros, um por cada ambiente, fica aqui o exemplo do ficheiro para o ambiente de "desenvolvimento", em que indica ao Terraform para salvar os ficheiros de state entro de uma storage account:
| backend-config-dev.tfvars | |
|---|---|
Template generico para todos os projectos¶
O projecto Terraform_Backend com o template que dá origem a todos os projectos de Terraform, sempre que for necessário criar um novo repositório, é clonado a partir deste e de seguida é adicionado os modulos que são para ser criados na infraestrutura.
O projecto contem os seguintes ficheiros:
-
.terraform-version: Indica qual a versão de terraform a usar (tanto localmente, como quando as pipelines de CI/CD forem executadas)
.terraform-version -
.auto.tfvars: É onde são colocadas todas as váriaveis transversais a todos os ambientes (não inclui secrets)
-
.env-dev.tfvars, .env-staging.tfvars, .env-prod.tfvars: É onde são colocadas cada variável de cada ambiente especifíco (não inclui secrets)
-
providers.tf: É onde são colocadas cada variável de cada ambiente especifíco (não inclui secrets)
-
variables.tf: É onde são definidas todas as váriáveis que o Terraform vai ler, incluíndo os secrets que serão injectados pelas pipelines durante a execução
-
.gitlab-ci.yml: Ficheiro de pipelines do Gitlab
.gitlab-ci.yml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
image: # Check last version here: https://hub.docker.com/_/alpine # The version to use in all pipelines is centralized in Gitlab Environment Variables name: alpine:$ALPINE_DOCKER_TAG variables: WORKSPACE: "TERRAFORM-PROJECT-TEMPLATE" # << REPLACE THIS FIELD <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<---- UPDATE THIS FIELD TF_VAR_gitlab_token: "${GL_TOKEN}" GIT_DOCKER_TAG: "2.36.0" # https://hub.docker.com/r/bitnami/git cache: key: primetag_gitlab_cache paths: - terraform-plugin-cache stages: - PrepareVersions - MergeRequest - GenerateBuildNumber - Plan - Apply - TagBuildNumber - TagVersion PrepareVersionsMR: stage: PrepareVersions only: - merge_requests script: # https://docs.gitlab.com/ee/ci/pipelines/job_artifacts.html#artifactsreportsdotenv # The .env file can’t have empty lines or comments (starting with #). # Key values in the env file cannot have spaces or newline characters (\n), including when using single or double quotes. - echo "TERRAFORM_DOCKER_TAG=$(head -1 .terraform-version)" >> versions.env - cat versions.env artifacts: reports: dotenv: versions.env PrepareVersionsMaster: stage: PrepareVersions only: - main script: # https://docs.gitlab.com/ee/ci/pipelines/job_artifacts.html#artifactsreportsdotenv # The .env file can’t have empty lines or comments (starting with #). # Key values in the env file cannot have spaces or newline characters (\n), including when using single or double quotes. - echo "TERRAFORM_DOCKER_TAG=$(head -1 .terraform-version)" >> versions.env - cat versions.env artifacts: reports: dotenv: versions.env PrepareVersionsDev: stage: PrepareVersions only: - /build/ except: - branches script: # https://docs.gitlab.com/ee/ci/pipelines/job_artifacts.html#artifactsreportsdotenv # The .env file can’t have empty lines or comments (starting with #). # Key values in the env file cannot have spaces or newline characters (\n), including when using single or double quotes. - echo "TERRAFORM_DOCKER_TAG=$(head -1 .terraform-version)" >> versions.env - cat versions.env artifacts: reports: dotenv: versions.env PrepareVersionsProd: stage: PrepareVersions only: - /releases/ except: - branches script: # https://docs.gitlab.com/ee/ci/pipelines/job_artifacts.html#artifactsreportsdotenv # The .env file can’t have empty lines or comments (starting with #). # Key values in the env file cannot have spaces or newline characters (\n), including when using single or double quotes. - echo "TERRAFORM_DOCKER_TAG=$(head -1 .terraform-version)" >> versions.env - cat versions.env artifacts: reports: dotenv: versions.env .job_template: &job_init terraform init -backend-config=terraform-backend/backend-config-"${ENV}".tfvars -var-file=".env-${ENV}.tfvars" && (terraform workspace select ${WORKSPACE}-${ENV} || terraform workspace new ${WORKSPACE}-${ENV}) .job_template: &job_validate terraform validate .job_template: &job_refresh terraform refresh -input=false -var-file=".env-${ENV}.tfvars" .job_template: &job_plan terraform plan -input=false -var-file=".env-${ENV}.tfvars" -out "planfile-${ENV}" # terraform plan -var "gcloud_project=youclap-$ENV" -var-file=$SECRETS_FILE -var "global_environment=$ENV" -input=false -out "planfile-$ENV" .job_template: &job_apply terraform apply -auto-approve -input=false "planfile-${ENV}" .job_template: &job_show terraform show PlanMRDev: image: name: hashicorp/terraform:$TERRAFORM_DOCKER_TAG entrypoint: [ "" ] stage: MergeRequest only: - merge_requests variables: ENV: "dev" ARM_CLIENT_ID: $ARM_CLIENT_ID_DEV ARM_CLIENT_SECRET: $ARM_CLIENT_SECRET_DEV ARM_SUBSCRIPTION_ID: $ARM_SUBSCRIPTION_ID_DEV ARM_TENANT_ID: $ARM_TENANT_ID_DEV script: - *job_init - *job_validate - *job_refresh - *job_plan artifacts: reports: terraform: planfile-${ENV} PlanMRProd: image: name: hashicorp/terraform:$TERRAFORM_DOCKER_TAG entrypoint: [ "" ] stage: MergeRequest only: - merge_requests # environment: # name: production variables: ENV: "prod" ARM_CLIENT_ID: $ARM_CLIENT_ID_PROD ARM_CLIENT_SECRET: $ARM_CLIENT_SECRET_PROD ARM_SUBSCRIPTION_ID: $ARM_SUBSCRIPTION_ID_PROD ARM_TENANT_ID: $ARM_TENANT_ID_PROD script: - *job_init - *job_validate - *job_refresh - *job_plan artifacts: reports: terraform: planfile-${ENV} GenerateBuildNumber: stage: GenerateBuildNumber image: bitnami/git:$GIT_DOCKER_TAG # environment: # name: development only: - main script: - git rev-list --count origin/main > .build_number - echo "Build number:"$(cat .build_number) artifacts: paths: - .build_number expire_in: 1 year PlanDev: image: name: hashicorp/terraform:$TERRAFORM_DOCKER_TAG entrypoint: [ "" ] stage: Plan # environment: # name: development variables: ENV: "dev" ARM_CLIENT_ID: $ARM_CLIENT_ID_DEV ARM_CLIENT_SECRET: $ARM_CLIENT_SECRET_DEV ARM_SUBSCRIPTION_ID: $ARM_SUBSCRIPTION_ID_DEV ARM_TENANT_ID: $ARM_TENANT_ID_DEV only: - main script: - *job_init - *job_validate - *job_refresh - *job_plan artifacts: reports: terraform: planfile-${ENV} PlanProd: image: name: hashicorp/terraform:$TERRAFORM_DOCKER_TAG entrypoint: [ "" ] stage: Plan variables: ENV: "prod" ARM_CLIENT_ID: $ARM_CLIENT_ID_PROD ARM_CLIENT_SECRET: $ARM_CLIENT_SECRET_PROD ARM_SUBSCRIPTION_ID: $ARM_SUBSCRIPTION_ID_PROD ARM_TENANT_ID: $ARM_TENANT_ID_PROD only: - main script: - *job_init - *job_validate - *job_refresh - *job_plan artifacts: reports: terraform: planfile-${ENV} ApplyDEV: image: name: hashicorp/terraform:$TERRAFORM_DOCKER_TAG entrypoint: [ "" ] stage: Apply # allow_failure: false environment: name: development variables: ENV: "dev" ARM_CLIENT_ID: $ARM_CLIENT_ID_DEV ARM_CLIENT_SECRET: $ARM_CLIENT_SECRET_DEV ARM_SUBSCRIPTION_ID: $ARM_SUBSCRIPTION_ID_DEV ARM_TENANT_ID: $ARM_TENANT_ID_DEV TF_VAR_service_principal_client_id: $ARM_CLIENT_ID_DEV TF_VAR_service_principal_client_secret: $ARM_CLIENT_SECRET_DEV TF_VAR_mysql_server_2_root_username: $MYSQL_SERVER_2_ROOT_USERNAME_DEV TF_VAR_mysql_server_2_root_password: $MYSQL_SERVER_2_ROOT_PASSWORD_DEV # when: manual only: - /build/ except: - branches script: - *job_init - *job_refresh - *job_plan - *job_apply - *job_show ApplyPROD: image: name: hashicorp/terraform:$TERRAFORM_DOCKER_TAG entrypoint: [ "" ] stage: Apply allow_failure: false # when: manual #environment: # name: production variables: ENV: "prod" ARM_CLIENT_ID: $ARM_CLIENT_ID_PROD ARM_CLIENT_SECRET: $ARM_CLIENT_SECRET_PROD ARM_SUBSCRIPTION_ID: $ARM_SUBSCRIPTION_ID_PROD ARM_TENANT_ID: $ARM_TENANT_ID_PROD only: - /releases/ except: - branches script: - *job_init - *job_refresh - *job_plan - *job_apply - *job_show TagVersionProd: stage: TagVersion image: registry.gitlab.com/juhani/go-semrel-gitlab:$GSG_DOCKER_TAG allow_failure: false when: manual variables: GSG_TAG_PREFIX: "releases/" only: - main script: - release next-version - release tag TagBuildNumberDev: stage: TagBuildNumber image: bitnami/git:$GIT_DOCKER_TAG # environment: # name: development only: - main script: - BUILD_NUMBER=`cat .build_number` - echo "Build number $BUILD_NUMBER" - git tag builds/$BUILD_NUMBER - git remote set-url origin https://gitlab-ci-token:$GL_TOKEN@gitlab.com/$CI_PROJECT_PATH.git/ - git push origin builds/$BUILD_NUMBER -
.gitmodules e terraform-backend: Submodule explicado no ponto anterior, responsavel pordefinir as configurações do backend do Terraform
Pipelines de CI/CD¶
As pipelines de CI/CD em todos os projectos de infraestrutura passam pelos seguintes estados:
- Quando é aberto um Merge Request por um programador correm os seguintes jobs:
- CodeSmellGlitch: Exporta um artefacto com o relatório do Glitch (code smell)
- TerraformValidate: Valida os ficheiros de Terraform para confirmar que são válidos (semelhante a um linter)
- PlanMRDev: Planeia que alterações vão ser aplicadas em DEV, caso este MR seja merged
- O ficheiro de planning é exportado para o Gitlab, de forma a dar feedback imediato aos reviewers
- PlanMRStaging: Só disponivel em alguns projectos, vamos ignorar por agora
- PlanMRProd: Planeia que alterações vão ser aplicadas em PROD, caso este MR seja merged, (e posteriorment seja enviado para produção)
- O ficheiro de planning é exportado para o Gitlab, de forma a dar feedback imediato aos reviewers
- Depois de um Merge Request ser aprovado e ser feito o Merge para o branch 'main', correm os seguintes jobs:
- GenerateBuildNumber: É gerado um build number (Exemplo: 1.2.3-rc1)
- PlanDev: Planeia novamente que alterações vão ser aplicadas em DEV, pois desde que o MR foi aberto até ter sido merged, podem ter ocorrido outras alterações à infraestrutura
- PlanProd: Planeia novamente que alterações vão ser aplicadas em DEV, pois desde que o MR foi aberto até ter sido merged, podem ter ocorrido outras alterações à infraestrutura
- TagBuildNumberDev: Adiciona automáticamente uma git tag ao código com a versão calculada no job GenerateBuildNumber (irá despoletar uma release automática para o ambiente de desenvolvimento)
- TagVersionProd: Este job é manual, fica à espera que um developer venha manualmente clicar num botão para ser calculada a proxima versão de produçào e adicionada essa tag ao código, despoletando uma release para produção automáticamente
- Sempre que for adicionada um tag com versão de DEV, executa os seguintes jobs:
- ApplyDEV: Aplica as alterrações no ambiente de desenvolvimento
- Sempre que for adicionada um tag com versão de PROD, executa os seguintes jobs:
- ApplyPROD: Aplica as alterrações no ambiente de produção
| MergeRequest | GenerateBuildNumber | Plan | Apply | TagBuildNumber | TagVersion | |
|---|---|---|---|---|---|---|
| Merge Request | -TerraformValidate -PlanMRDev -PlanMRProd |
|||||
| After Merge | -GenerateBuildNumber | -PlanDev -PlanProd |
-TagBuildNumberDev | -TagVersionProd | ||
| TagBuildNumber | -ApplyDEV | |||||
| TagVersion | -ApplyPROD |