💡 테라폼으로 구성된 인프라 환경은 지속적으로 안정성과 정상 상태를 유지하는 작업을 해주어야 함.
해당 챕터에서는 이를 위한 방법 들을 알아보고자 함.

9.1. 배포 단위 관리

변경을 위해 명시적으로 테라폼 코드를 수정하고 기존의 State 와 비교함으로써 일관성과 안정성을 제공하지만, 지속적인 변경 요구사항State 가 실제 인프라 상태와 다른 경우가 발생할 수 있음.

  • Ex. AWS Security Group 의 Rule, 테라폼으로 관리되던 리소스의 매뉴얼(manual) 조작

🙌 테라폼에서 좋은 기능이라고 생각하는 것 중 하나

리소스가 배포된 상태에서 리소스/모듈/데이터 블록의 이름이 변경되면 테라폼 상에서의 주소가 변경되기 때문에 기존 리소스를 제거하고 새로 생성하려고 함

→ 구성이 변경되지 않았다면 새로 생성할 필요가 없기 때문에 State 상의 주소를 변경 해줘야 하고 이때 terraform state 명령어를 사용

  • terraform state list: 현재 상태 기준 리소스 주소 확인
    • -id 옵션을 통해 실제 ID 값(ex. 인스턴스 id) 을 확인 할 수 있음
  • terraform state mv <source> <destination> : State 상의 리소스 주소를 새로운 주소로 변경

EX. 리소스 블록 이름 변경

아래와 같이 수정 후 plan 을 실행해보면 기존 리소스는 제거하고 새 리소스가 추가되는 계획이 출력됨

# 기존 코드
resource "null_resource" "configure-cat-app" { ... }

# 변경 코드
resource "null_resource" "configure_cat_app" { ... }
Terraform will perform the following actions:

  # null_resource.configure-cat-app will be destroyed
  # (because null_resource.configure-cat-app is not in configuration)
  - resource "null_resource" "configure-cat-app" {
      - id = "8477361916687168569" -> null
    }

  # null_resource.configure_cat_app will be created
  + resource "null_resource" "configure_cat_app" {
      + id = (known after apply)
    }

Plan: 1 to add, 0 to change, 1 to destroy.

terraform state mv

새로운 주소로 변경한 후, plan 시 변경 사항이 없다는 계획을 확인 할 수 있음

$ terraform state mv 'null_resource.configure-cat-app' 'null_resource.configure_cat_app'
Move "null_resource.configure-cat-app" to "null_resource.configure_cat_app"
Successfully moved 1 object(s).

$ terraform plan
No changes. Your infrastructure matches the configuration.

배포된 리소스를 테라폼이 아닌 다른 경로(ex. 콘솔) 를 통해 변경한 경우 실제 인프라와 State 간의 차이가 발생하고, 아래 방법 중 한가지를 선택하여 조치할 수 있음

  • 코드 구성으로 리소스 재 프로비저닝
  • State 를 실제 인프라의 상태로 업데이트 하고 코드 수정

EX. AWS 콘솔에서 SG 인바운드 규칙 수정

[sg 인바운드 수정 전]
[sg 인바운드 수정 전]
[sg 인바운드 수정 후]
[sg 인바운드 수정 후]

위와 같이 콘솔에서 직접 리소스를 수정하고 terraform plan 을 실행하면, 테라폼은 리소스(SG)를 코드 구성으로 변경 하려는 계획을 출력함. (즉, 직접 추가된 규칙은 제거되는 계획 표시)

Terraform will perform the following actions:

  # aws_security_group.hashicat will be updated in-place
  ~ resource "aws_security_group" "hashicat" {
        id                     = "sg-06614be8af3897fe4"
      ~ ingress                = [
          - {
              - cidr_blocks      = [
                  - "0.0.0.0/0",
                ]
              - from_port        = 22
              - ipv6_cidr_blocks = []
              - prefix_list_ids  = []
              - protocol         = "tcp"
              - security_groups  = []
              - self             = false
              - to_port          = 22
                # (1 unchanged attribute hidden)
            },
          - {
              - cidr_blocks      = [
                  - "0.0.0.0/0",
                ]
              - from_port        = 443
              - ipv6_cidr_blocks = []
              - prefix_list_ids  = []
              - protocol         = "tcp"
              - security_groups  = []
              - self             = false
              - to_port          = 443
                # (1 unchanged attribute hidden)
            },
          - {
              - cidr_blocks      = [
                  - "0.0.0.0/0",
                ]
              - from_port        = 8080
              - ipv6_cidr_blocks = []
              - prefix_list_ids  = []
              - protocol         = "tcp"
              - security_groups  = []
              - self             = false
              - to_port          = 8080
                # (1 unchanged attribute hidden)
            },
          - {
              - cidr_blocks      = [
                  - "0.0.0.0/0",
                ]
              - from_port        = 80
              - ipv6_cidr_blocks = []
              - prefix_list_ids  = []
              - protocol         = "tcp"
              - security_groups  = []
              - self             = false
              - to_port          = 80
                # (1 unchanged attribute hidden)
            },
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + from_port        = 22
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 22
            },
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + from_port        = 443
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 443
            },
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + from_port        = 80
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 80
            },
        ]
        name                   = "tf101-security-group"
        tags                   = {
            "Name" = "tf101-security-group"
        }
        # (8 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

terraform refresh, terraform state show

만약 직접 수정한 사항을 유지해야 한다면 refresh 를 통해 State 를 실제 인프라 상태와 맞춰주고 코드 또한 변경된 사항에 맞게 수정해줘야 함

  • terraform state show <리소스 주소> 를 통해 코드를 어떻게 변경 해야할 지 참고할 수 있음
$ terraform state show aws_security_group.hashicat

# aws_security_group.hashicat:
resource "aws_security_group" "hashicat" {
    arn                    = "arn:aws:ec2:ap-northeast-2:471112597139:security-group/sg-06614be8af3897fe4"
    description            = "Managed by Terraform"
    egress                 = [
        {
            cidr_blocks      = [
                "0.0.0.0/0",
            ]
            description      = null
            from_port        = 0
            ipv6_cidr_blocks = []
            prefix_list_ids  = []
            protocol         = "-1"
            security_groups  = []
            self             = false
            to_port          = 0
        },
    ]
    id                     = "sg-06614be8af3897fe4"
    ingress                = [
        {
            cidr_blocks      = [
                "211.206.2.93/32",
            ]
            description      = null
            from_port        = 22
            ipv6_cidr_blocks = []
            prefix_list_ids  = []
            protocol         = "tcp"
            security_groups  = []
            self             = false
            to_port          = 22
        },
        {
            cidr_blocks      = [
                "211.206.2.93/32",
            ]
            description      = null
            from_port        = 443
            ipv6_cidr_blocks = []
            prefix_list_ids  = []
            protocol         = "tcp"
            security_groups  = []
            self             = false
            to_port          = 443
        },
        {
            cidr_blocks      = [
                "211.206.2.93/32",
            ]
            description      = null
            from_port        = 80
            ipv6_cidr_blocks = []
            prefix_list_ids  = []
            protocol         = "tcp"
            security_groups  = []
            self             = false
            to_port          = 80
        },
    ]
    name                   = "tf101-security-group"
    name_prefix            = null
    owner_id               = "471112597139"
    revoke_rules_on_delete = false
    tags                   = {
        "Name" = "tf101-security-group"
    }
    tags_all               = {
        "Name"        = "tf101-security-group"
        "environment" = "dev"
        "name"        = "tf101-vpc-ap-northeast-2"
    }
    vpc_id                 = "vpc-0edee5b602a392df5"
}

📍 [실습 시 책과 달랐던 점]

  • plan 을 먼저 하면 state 에 이미 변경사항이 반영되어 따로 refresh 를 하지 않아도 되었음
  • refresh 를 하더라도 변경 내용이 터미널에 출력 되지는 않았음 (State 파일에 변경 사항은 잘 반영됨)
🙌 실제로 코드로써 인프라를 관리 하다보면, 하나의 모듈에 관리하는 리소스가 늘어남에 따라
분리해야 하는 필요가 생기곤 하기 때문에 이번 내용을 잘 숙지하면 도움이 많이 될 것 같다고 생각함

테라폼 코드 구성 시 일반적으로는 단일 모듈에서 전체 리소스를 프로비저닝 하지만, 필요에 따라 워크스페이스를 분리 하며 이미 존재하는 리소스에 영향이 없도록 테라폼 코드와 State 가 모두 분리 해야 함

워크스페이스를 분리할 때는 아래 기준에 따라 범위를 결정하게 됨 (단순히 같은 리소스끼리 분리 X)

  • 작업자 또는 팀 간 R&R
  • 리소스 배포 단위

💡 실습 코드: https://github.com/hanaldo1/its-terraform-study/tree/feature/chapter-09

i. 로컬 작업 환경 구성

기존의 루트 모듈을 분리할 워크스페이스에 맞춰 각각의 루트 모듈로 분리

  • Ex. terraform-all → terraform-network, terraform-compute

ii. 워크스페이스 잠그기 [optional ⚠️]

단위가 큰 작업을 할 때는 최신 상태에서 변경사항 없도록 워크스페이스를 잠글 수 있음

  • 다만 백엔드가 locking 을 지원해야 하며, 지원 여부는 [공식 문서] 에서 확인 가능

iii. State 가져오기/보내기

  1. TFC와 같은 백엔드를 사용하고 있었다면, 백엔드에 저장된 State 를 로컬에 복사
    1. $ terraform state pull > terraform.tfstate
  2. 1번에서 받은 State 파일과 함께 기존 루트 모듈의 코드를 분리된 각 루트 모듈에 복사
    1. 백엔드 구성의 경우, 분리 작업 전까지는 주석 처리 필요
.
├── terraform-all
│   ├── README.md
│   ├── terraform.tfstate
│   ├── files
│   │   └── deploy_app.sh
│   ├── main.tf
│   ├── outputs.tf
│   ├── terraform.tfvars
│   └── variables.tf
├── terraform-compute (NEW)
│   ├── terraform.tfstate
│   ├── files
│   │   └── deploy_app.sh
│   ├── main.tf
│   ├── outputs.tf
│   ├── terraform.tfvars
│   └── variables.tf
└── terraform-network (NEW)
    ├── terraform.tfstate
    ├── files
    │   └── deploy_app.sh
    ├── main.tf
    ├── outputs.tf
    ├── terraform.tfvars (optional)
    └── variables.tf

iv. 분리 범위에 따라 코드 수정 [반복]

기존 코드에서 해당 모듈에 필요한 리소스만 남기고 제거 (+ 사용하지 않는 input, output 변수)

v. State 리소스 주소 삭제 [반복]

  1. 코드에서 제거한 리소스는 State 상에서도 제거 (→ 해당 루트 모듈에서 관리 X)

    Ex. terraform state rm <리소스 주소>

     $ terraform state rm 'null_resource.configure_cat_app[0]'
     $ terraform state rm 'aws_eip_association.hashicat[0]'
     $ terraform state rm 'aws_eip.hashicat[0]'
     $ terraform state rm 'aws_eip.hashicat[0]'
     $ terraform state rm 'aws_instance.hashicat[0]'
     $ terraform state rm 'data.aws_ami.ubuntu'
     $ terraform state rm 'aws_key_pair.hashicat'
     $ terraform state rm 'tls_private_key.hashicat'
    
  2. plan 을 통해 변경 사항이 없는지 확인 후, apply 로 output 갱신

  3. [필요 시] 로컬 State 를 백엔드로 마이그레이션

    1. 백엔드 구성 주석 해제 후, terraform init 실행
    2. 또는 init 시 마이그레이션 여부에 ‘no’ 를 입력하고, terraform state push 를 통해 수동으로 마이그레이션

vi. remote_state 활용

만약 다른 모듈로 분리된 리소스를 참조해야 한다면, 필요한 값을 output 으로 내보내고terraform_remote_state 데이터 소스를 사용하여 참조

[TFC 사용 시] 다른 워크스페이스에서 참조할 수 있도록 Remote state sharing 설정 추가 필요

[TFC Remote state sharing 설정]
[TFC Remote state sharing 설정]


  • 예시 1) network 루트 모듈 - output 추가

      output "subnet_id" {
        value = aws_subnet.hashicat.id
      }
        
      output "sg_id" {
        value = aws_security_group.hashicat.id
      }
    
  • 예시 2) compute 루트 모듈 - terraform_remote_state 사용

    • 참조 방법: data.terraform_remote_state.<이름>.outputs.<이름>
      data "terraform_remote_state" "network" {
        backend = "remote"
        
        config = {
          organization = "hanaldo-tf"
          hostname = "app.terraform.io"
          workspaces = {
            name = "terraform-network"
          }
        }
      }
        
      resource "aws_instance" "hashicat" {
      	...
        subnet_id = data.terraform_remote_state.network.outputs.subnet_id
        vpc_security_group_ids = [data.terraform_remote_state.network.outputs.sg_id]
      }
    

워크스페이스 병합 시에는 코드 분할과 State 복제 및 삭제를 반대로 진행 (→ 즉, 코드 및 State 병합)

  1. 분리했던 리소스를 병합할 루트 모듈 생성
  2. 병합할 루트 모듈에 분리 되어있던 코드 병합
  3. 각 분리된 모듈의 백엔드에 저장된 State 를 로컬에 가져오고, 병합할 루트 모듈 State 에 병합 1. 한 모듈의 terraform.tfstate 파일을 병합 모듈에 복사 2. terraform state mv 를 통해 다른 모듈의 State 복사
    1. EX) terraform state mv -state='<분리 모듈 경로>/terraform.tfstate' -state-out='<병합 모듈 경로>/terraform.tfstate' '<리소스 주소>' '<리소스 주소>’


9.2. 기존 리소스를 테라폼으로 관리

이미 수동으로 프로비저닝한 리소스를 테라폼을 통해 관리를 하게 된다면 해당 리소스를 테라폼 State 로 가져온 후 (Import), 리소스 정의에 맞게 코드를 구성해야 함

terraform import 기능을 사용하면 기 생성된 리소스를 테라폼으로 관리할 수 있음.

📍단, 리소스 별로 import 시 필요한 정보(ex. AWS EC2 id) 가 다를 수 있고 import 를 지원하지 않을 수도 있기 때문에 작업 전 [Terraform Registry 페이지] 에서 프로바이더 별 문서를 잘 확인 해야 함.

0. AWS 콘솔에서 수동으로 생성한 보안 그룹

i. 테라폼 구성 준비 및 import

실제 리소스와 연결 될 리소스 블록을 선언하고, import 를 통해 State 에 해당 리소스 업데이트

  • terraform import <리소스 주소> <리소스 ID>
resource "aws_security_group" "import_sg" {} # 리소스 블록만 준비
$ terraform import aws_security_group.import_sg sg-02a88c90deec839a6

aws_security_group.import_sg: Importing from ID "sg-02a88c90deec839a6"...
aws_security_group.import_sg: Import prepared!
  Prepared aws_security_group for import
aws_security_group.import_sg: Refreshing state... [id=sg-02a88c90deec839a6]

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.

ii. 리소스 정의에 맞춰 테라폼 코드 수정

import 하고 난 뒤에는 실제 리소스 구성에 맞춰서 코드 또한 작성 (terraform state show 활용)

import 가 완료된 후, 리소스 구성을 변경 했을 때 리소스를 테라폼으로 관리할 수 있는지 확인해보기!

  • 다만, state show 출력 결과에서 리소스 정의 시 필요한 인수만 골라서 사용할 필요 있음

    $ terraform state show aws_security_group.import_sg
      
    resource "aws_security_group" "import_sg" {
        arn         = "arn:aws:ec2:ap-northeast-2:471112597139:security-group/sg-02a88c90deec839a6"
        description = "security group for terraform 101 import practice"
        egress      = []
        id          = "sg-02a88c90deec839a6"
        ingress     = []
        name        = "tf101-import-sg"
        name_prefix = null
        owner_id    = "471112597139"
        tags        = {}
        tags_all    = {}
        vpc_id      = "vpc-03df2fb45155c6b29"
    }
    
resource "aws_security_group" "import_sg" {
  name        = "tf101-import-sg"
  description = "security group for terraform 101 import practice"
  vpc_id      = "vpc-03df2fb45155c6b29"
  ingress     = []
  egress      = []
}

테라폼으로 가져올 리소스가 적다면 import 를 활용하는 것만으로도 충분하지만, 만약 그 수가 많다면 일일이 import 하는 것은 번거로울 수 있음.

https://github.com/GoogleCloudPlatform/terraformer

terraformer 을 사용하면 조건에 맞는 리소스를 모두 조회하여 테라폼 코드화 및 State 를 구성 할 수 있음


9.3. 리팩토링과 모듈화

한번 작성한 테라폼 코드는 시간이 지남에 따라, 구성 변경 필요에 따라 수정 될 수 있기 때문에 지속적으로 관리가 필요하며, 동일한 리소스 구성을 반복적으로 사용 해야 하는 경우 모듈화의 필요가 생김.

💡 리팩토링과 모듈화 방식은 테라폼을 사용하는 조직에 따라 달라질 수 있기 때문에, 여기서는 어떤 예시들이 있는지 정도만 소개할 예정

i. 변수 설명 추가 및 validation 추가

input 변수의 경우 작업자에 따라 다른 값이 입력 될 수 있기 때문에, 의도한 값이 들어갈 수 있도록 설명을 추가하고, validation 설정을 통해 의도한 값이 아닌 경우 에러를 발생 시킴으로써 예기치 못한 수정 등을 막음

ii. count → for_each

리스트를 count 에 사용하는 경우, 중간 값이 삭제 되면 이후 요소로 정의된 리소스도 삭제 후 재생성 되기 때문에 사용에 주의해야 함. (3.9.1 절 참고) → for_each 권장

iii. 불필요한 input 변수 → local 변수

input 변수를 사용하는 경우 입력 값에 따라 변경사항이 발생할 수도 있기 때문에, 고정되게 사용해야 하는 값은 local 변수를 사용하는 것이 권장 되며, 데이터 소스를 활용하는 것도 하나의 방법이 될 수 있음.

iv. 중복 상수 값 대체

중복으로 사용 되는 상수값 (ex. 리소스 이름 앞에 붙는 prefix) 은 변수를 활용하거나, cidrsubnet 과 같은 [함수] 를 사용하면 구성 에러와 같은 실수를 방지할 수 있음.

v. 리소스/데이터 소스 이름 내 중첩 선언 제거

resource “aws_subnet” “sub_test” 처럼 이름에 유형이 중복되어 들어가는 경우, resource “aws_subnet” “test” 로 수정

모듈화는 ‘리소스 구성에 대한 템플릿을 만들어 반복적으로 사용’ 하고자 하는 목적으로, 1) 동일 리소스가 아닌 관리 목적에 따라 범위를 나누고 2) 반복적 사용이 불가능한 구성은 제외 하여 진행함.

  • 입력 변수가 많으면 이에 따른 변동성이 높아지기 때문에 최소화 하는 것이 좋음
  • 선택적으로 생성해야 하는 모듈 또는 리소스(모듈 내) 의 경우, input 변수를 통해 제어할 수 있도록 구성
    • EX) count = var.enable ? 1: 0

모듈화 과정

  1. 모듈화 대상이 되는 리소스, 데이터 소스 코드 이동
    1. 필요 시, 코드 옮기는 과정 중간중간 리팩토링 진행
  2. 모듈에 필요한 input 변수 및 내보낼 output 변수 (다른 모듈에서 참조 필요 값) 정의
  3. 루트 모듈에 모듈 선언 (+ 모듈 input 변수에 맞춰 인수 정의)
  • 예시) 모듈화 구조

      ├── *.tf
      ├── modules
         ├── common # 네트워크(vpc, subnet, sg), 키페어 
            ├── ...
         └── eks
            ├── ...
      └── terraform.tfvars
    

구성한 모듈을 공통 템플릿으로 사용하기 위해서는 VCS 태깅을 이용하거나 TFC Registry 를 이용

module "vpc" {
  source = "github.com/hanaldo1/its-terraform-study//chapter-09//module-management//vpc?ref=v0.0.1"
  ...
}
[Github 태그 생성]
[Github 태그 생성]

📍 실제 실습 시 ?ref=<브랜치> 는 인식을 못하고, 태그를 사용해야만 했음

📍 만약 레포지토리 내 서브 디렉토리로 모듈을 구성 했다면 //<디렉토리> 로 지정 가능

  • ?ref=<브랜치> 에러

      Could not download module "vpc" (main.tf:12) source code from
       "git::https://github.com/hanaldo1/its-terraform-study.git?ref=feature": error downloading
       'https://github.com/hanaldo1/its-terraform-study.git?ref=feature': /opt/homebrew/bin/git exited with 1: error: pathspec
       'feature' did not match any file(s) known to git
    
[Github 릴리즈 생성]
[Github 릴리즈 생성]
[TFC Registry]
[TFC Registry]
module "vpc" {
  source  = "app.terraform.io/hanaldo-tf/module/aws"
  version = "0.0.1"
	...
}

📍 레포지토리 이름은 terraform-<provider>-<name> 형식을 따라야만 TFC Registry 에서 연동 시 인식이 됨


9.4. 문서화

테라폼으로 관리하는 리소스가 많아지고 여러 사람이 작업 함에 따라 작업자의 의도와는 다르게 해석될 수 있음.

이를 방지하고자 작업자의 의도 및 주의 사항 들을 문서로 작성하여 가이드를 제시하는 것이 좋음.

  • 루트 모듈
    • 프로바이더 버전 정보
    • 프로비저닝 대상
    • 필요 자식 모듈 및 버전
  • 자식 모듈 (템플릿 모듈)
    • 해당 모듈을 사용(작성) 하는 방법
    • 모듈 구성 시 필요 변수 및 주의 사항
    • 모듈 내 입력 변수에 따른 변화가 큰 경우 이에 대한 주의 사항 및 추가 사용법도 작성


9.5. 개발 워크플로와 운영 이관

테라폼으로 지속적으로 인프라를 구성하고 운영/관리하기 위해서는, 이를 위한 워크플로우 설계 필요

  • 즉, 테라폼 코드를 개발하고 검증하고 실제 운영환경에 적용 하기까지의 체계적인 과정을 세울 필요가 있음
💡 다만, 이 또한 조직에 따라 달라질 수 있기 때문에, 여기서는 어떤 예시를 들었는지 정도만 소개할 예정

서비스 개발을 위한 프로젝트 개발 워크플로에서 테라폼 코드는 VCS 를 통해 버전 관리

이때, 브랜치 기반으로 프로비저닝 환경을 분리하여 관리

  • 개발: 테라폼 코드 작성 및 수정이 일어나는 브랜치. 작업자 별 브랜치를 생성 후 PR-merge
  • 검수: 품질 검증을 위해 개발 브랜치 병합 → 검수 환경 프로비저닝 (해당 브랜치부터는 코드 수정 X)
  • 운영: 검수 브랜치 병합 → 운영 환경 프로비저닝

모듈 개발 워크플로에서 또한 테라폼 코드는 VCS 를 통해 관리하며, 태깅을 통해 버전을 릴리즈 하며 사용

  • 이렇게 릴리즈 된 모듈을 서비스 개발을 위한 테라폼 코드 작성 시 사용 (Ex. 라이브러리와 같이)

운영이관

  1. 개발 단계에서 구성한 테라폼 코드를 운영환경에 적용하는 운영이관 주체 지정
  2. 서비스/프로젝트 규모, 운영 이관 대상 등을 고려하여 운영이관 시점 정의

변경 관리

  1. 표준 변경 관리
    1. 일상적인 변경사항(ex. 허용 IP 추가, 리소스 속성) 등을 반영하는 경우
  2. 수동 변경 관리
    1. 장애가 발생하여 긴급 수정 필요로, 부득이하게 콘솔을 통해 코드를 거치지 않고 직접 변경하는 경우
    2. 이와 같은 경우가 발생한 경우 사후 조치가 중요함
      1. 수동으로 반영한 변경사항 기록
      2. State 와 실제 리소스 구성의 편차 수정 (9.1.2 참고)
      3. 변경사항 공유 및 표준 적용 여부 확인
  3. 예시) 변경 요청 관리
    1. 변경 요청 등록 및 접수
      1. 사내 업무 관리 툴 등을 활용하여 요청 및 담당자 요청 내용 검토
    2. 변경 승인 및 적용
      1. 변경 사항 테라폼으로 개발 및 PR 을 통한 코드 리뷰 진행 → 프로비저닝
    3. 변경 사항 및 처리 현황 공유
      1. 사내 업무 관리 툴 및 메신저를 통해 처리 완료 여부 공유