LabHub
배우기 러닝패스 코스

Infrastructure as Code

desired / actual / planの三分割を作る

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

이 실습은 진짜 VM 에서 돕니다

이 상자는 파드가 아니라 KubeVirt 가 띄운 가상머신입니다. 리눅스 커널이 따로 돌고 systemd 가 실제로 서비스를 관리하며, docker 는 흉내가 아니라 진짜 도커 엔진입니다. docker run 으로 띄운 컨테이너는 실제로 프로세스가 되고 docker execdocker logs 도 그대로 동작합니다.

예전에는 이 실습이 파드 안에서 돌았습니다. 커널 권한을 전부 내려놓은 상자라 컨테이너를 띄우는 단계가 막혀 있었고, 그래서 이미지 아카이브를 직접 풀어 보는 우회로 배웠습니다. 이제 우회가 필요 없습니다.

알아 둘 것이 둘 있습니다.

목표

원하는 상태(desired), 현재 상태(actual), 둘의 차이(plan)라는 3분할 구조를 직접 만들고, 드리프트가 어떻게 감지되고 수렴되는지 확인한다.

왜 중요한가

IaC 도구가 화면에 뿌리는 + create, - destroy, ~ update 는 마법이 아니라 두 상태를 비교한 결과다. 이 구조를 손으로 한 번 만들어 보면 plan 을 읽는 눈이 달라진다. 특히 자동화에서는 결과를 종료코드로 받아야 하므로 0 은 변경 없음, 1 은 오류, 2 는 변경 있음이라는 관례를 그대로 따른다. 3축 모델로 말하면 코드는 Desired, 상태 파일은 Last Known, 실제 인프라는 Actual 이고, 셋 중 둘이라도 어긋나면 드리프트다. GitOps 의 자가 치유도 결국 이 비교를 반복해서 돌리는 일이다.

단계

  1. /root/iac1/desired.yaml 에 원하는 상태를 선언한다. 최상위에 containers: 한 줄을 두고 그 아래 항목 2개를 둔다. 이름은 각각 iac-webiac-cache 이고, 항목마다 image: 를 한 줄씩 적는다(파일 전체에서 image: 는 정확히 2줄). 이미지는 파드에 미리 있는 alpine:3.20, busybox:1.36, python:3.12-alpine, nginx:1.27-alpine 중에서만 고른다.
  2. /root/iac1/parse.sh 를 만들어 그 YAML 을 /root/iac1/desired.json 으로 변환한다. JSON 모양은 {"containers":[{"name":"...","image":"..."}, ...]} 이고 원소는 2개, image 값이 비어 있으면 안 된다.
  3. /root/iac1/actual.sh 를 만든다. 지금 실제로 떠 있는 관리 대상 컨테이너를 같은 모양의 JSON 으로 표준출력에 낸다. 각 원소에는 nameimage 가 모두 있어야 한다. 관리 대상은 이름이 iac- 로 시작하는 컨테이너만이며, 이미지 문자열은 localhost/docker.io/library/ 접두를 벗겨 desired 와 같은 표기로 맞춘다.
  4. /root/iac1/plan.sh <desired.json> <actual.json> 을 만든다. desired 에만 있으면 + create <이름>, actual 에만 있으면 - destroy <이름>, 양쪽에 있는데 image 가 다르면 ~ update <이름> 을 한 줄씩 출력한다. 차이가 하나도 없으면 no changes 를 출력하고 종료코드 0 을 낸다. 차이가 있으면 종료코드 2 를 낸다(0=변경 없음, 1=오류, 2=변경 있음).
  5. /root/iac1/apply.sh 를 만들어 실제 상태를 선언에 맞춘다. 끝나면 desired 에 적힌 두 컨테이너가 모두 running 이어야 한다.
  6. 아무것도 바꾸지 않은 채 plan 을 다시 돌려 /root/iac1/plan2.txt 에 저장한다. 이 파일에는 no changes 가 있고 +, -, ~ 로 시작하는 줄이 하나도 없어야 한다.
  7. 코드 밖에서 상태를 흔든다. docker rm -f iac-cache 로 컨테이너 하나를 지운 뒤 plan 을 돌려 /root/iac1/drift.txt 에 저장한다. 변경 줄은 정확히 하나여야 하고 그 줄은 + create iac-cache 여야 한다.
  8. 다시 apply.sh 를 돌려 수렴시키고, 그 뒤의 plan 을 /root/iac1/plan3.txt 에 저장한다. no changes 가 나오고 두 컨테이너가 모두 running 이어야 한다.

참고

원하는 상태 선언하기

/root/iac1/desired.yaml 에 원하는 상태를 선언한다. 최상위에 containers: 한 줄을 두고 그 아래 항목 2개를 둔다. 이름은 각각 iac-webiac-cache 이고, 항목마다 image: 를 한 줄씩 적는다(파일 전체에서 image: 는 정확히 2줄). 이미지는 파드에 미리 있는 alpine:3.20, busybox:1.36, python:3.12-alpine, nginx:1.27-alpine 중에서만 고른다.

/root/iac1/desired.yaml 최상위에 containers: 한 줄을 두고 그 아래 항목 2개를 둡니다. 이름은 정확히 iac-webiac-cache, 항목마다 image: 한 줄씩(총 2줄)입니다. 오프라인이라 이미지는 미리 있는 것만 씁니다.

선언을 기계가 읽는 형식으로

/root/iac1/parse.sh 를 만들어 그 YAML 을 /root/iac1/desired.json 으로 변환한다. JSON 모양은 {"containers":[{"name":"...","image":"..."}, ...]} 이고 원소는 2개, image 값이 비어 있으면 안 된다.

YAML 파서가 없어도 됩니다. grep/sed/awk 로 name 과 image 값을 뽑아 jq -n 으로 {containers:[...]} 를 조립하세요. /root/iac1/desired.json 의 원소는 2개이고 image 가 비면 안 됩니다.

현재 상태 읽어 오기

/root/iac1/actual.sh 를 만든다. 지금 실제로 떠 있는 관리 대상 컨테이너를 같은 모양의 JSON 으로 표준출력에 낸다. 각 원소에는 nameimage 가 모두 있어야 한다. 관리 대상은 이름이 iac- 로 시작하는 컨테이너만이며, 이미지 문자열은 localhost/docker.io/library/ 접두를 벗겨 desired 와 같은 표기로 맞춘다.

actual.sh 는 표준출력으로 desired 와 같은 모양의 JSON 을 냅니다. 관리 대상은 이름이 iac- 로 시작하는 컨테이너만입니다. podman 이 붙이는 localhost/, docker.io/library/ 접두는 벗겨 desired 와 표기를 맞추세요.

차이 계산하기

/root/iac1/plan.sh <desired.json> <actual.json> 을 만든다. desired 에만 있으면 + create <이름>, actual 에만 있으면 - destroy <이름>, 양쪽에 있는데 image 가 다르면 ~ update <이름> 을 한 줄씩 출력한다. 차이가 하나도 없으면 no changes 를 출력하고 종료코드 0 을 낸다. 차이가 있으면 종료코드 2 를 낸다(0=변경 없음, 1=오류, 2=변경 있음).

plan.sh <desired.json> <actual.json> 은 desired 에만 있으면 + create <이름>, actual 에만 있으면 - destroy <이름>, 이미지가 다르면 ~ update <이름> 을 출력합니다. 기호와 낱말 사이는 공백입니다. 차이가 없으면 no changes 와 종료코드 0.

선언한 상태 만들기

/root/iac1/apply.sh 를 만들어 실제 상태를 선언에 맞춘다. 끝나면 desired 에 적힌 두 컨테이너가 모두 running 이어야 한다.

apply.sh 는 plan 이 말한 것만 실행하면 됩니다. alpine:3.20 처럼 바로 끝나는 이미지는 tail -f /dev/null 같은 명령을 줘야 계속 running 으로 남습니다.

다시 적용해도 변화 없음

아무것도 바꾸지 않은 채 plan 을 다시 돌려 /root/iac1/plan2.txt 에 저장한다. 이 파일에는 no changes 가 있고 +, -, ~ 로 시작하는 줄이 하나도 없어야 한다.

아무것도 바꾸지 않은 상태에서 plan 을 다시 돌려 /root/iac1/plan2.txt 에 저장하세요. no changes 만 있고 +, -, ~ 로 시작하는 줄이 하나도 없어야 합니다. 채점은 desired.json 과 actual.sh 결과를 다시 비교하므로 이미지 표기가 한 글자만 달라도 실패합니다.

밖에서 생긴 변경 잡아내기

코드 밖에서 상태를 흔든다. docker rm -f iac-cache 로 컨테이너 하나를 지운 뒤 plan 을 돌려 /root/iac1/drift.txt 에 저장한다. 변경 줄은 정확히 하나여야 하고 그 줄은 + create iac-cache 여야 한다.

docker rm -f iac-cache 로 코드 밖에서 상태를 흔든 뒤 plan 을 /root/iac1/drift.txt 에 저장합니다. 변경 줄은 정확히 하나, + create iac-cache 여야 합니다. 하나만 지웠는데 여러 줄이 나오면 actual.sh 가 관리 대상을 잘못 고르고 있다는 뜻입니다.

자동 수렴

다시 apply.sh 를 돌려 수렴시키고, 그 뒤의 plan 을 /root/iac1/plan3.txt 에 저장한다. no changes 가 나오고 두 컨테이너가 모두 running 이어야 한다.

다시 apply 한 뒤의 plan 을 /root/iac1/plan3.txt 에 저장합니다. no changes 가 나오고 두 컨테이너가 모두 running 이면 GitOps 컨트롤러가 하는 자가 치유를 손으로 재현한 셈입니다.