https://kko.kakao.com/Ek5L5W4iqg

돈골약방

서울 성동구 성수일로8길 28

map.kakao.com


돼지국밥을 메인으로 하는 프렌차이즈이다.
가격은 7900원꼴로 아주 저렴하다


물은 감초차가 나온다.
오묘한 맛이지만 취향은 아니었다


무난한 돼기고기곰탕이다.
공기밥을 따로 시키는 건가 고민했는데
안에 들어가서 나오더라.

여러분도 한입 드셔라


성수동 근처 밥집치고
매우 저렴하며, 준수한 퀄리티를 자랑한다.

강추한다.

'라이프' 카테고리의 다른 글

동아제약 얼박사  (1) 2025.07.27
미미램양꼬치  (4) 2025.07.25
고오스 초코케익  (0) 2025.07.24
싹수가 노란 점자블록  (1) 2025.07.22
작심 스터디카페 삼송점  (2) 2025.07.22

이전 포스트

https://decompression.tistory.com/12

 

컨테이너 런타임 만들기 : Phase 1

컨테이너의 동작을 이해하기 위해 컨테이너 런타임을 만들어본다. 세상에 컨테이너 런타임이라고 알려진 소스들은 너무 많다.containerd, cri-o, kata container, runc, crun, podman 등등.. 그러나 이들은 같

decompression.tistory.com

 

전 글에서는 동작하는 컨테이너 데모를 만들어보았다.

 

이제 살을 붙여갈 차례인데 어떤 것부터 수행할지 고민하다가

역시 cli를 먼저 구현하려고 한다.

 

golang 에코시스템에는 spf13/cobra, urfave/cli 등 좋은 라이브러리들이 많지만

여기서는 urfave/cli를 사용하겠다.

 

구현해야 할 명령어의 목록은 이렇다. 이는 OCI Runtime Spec을 바탕으로 한다.

  • State
  • Create
  • Start
  • Kill
  • Delete

더불어 create과정에서 컨테이너가 실행할 init까지 합쳐 총 6개의 커맨드를 구현한다.

물론 내부 로직들은 차차 구현해 나가도록 하고, 구조만 잡아보자.

 

먼저 cmd 디렉터리를 만들어 커맨드들을 몰아넣었다.

 

각 파일들의 내용은 별 거 없다. 모두 아래와 같은 간단한 스텁이다.

package cmd

import (
	"context"

	"github.com/urfave/cli/v3"
)

var DeleteCommand = &cli.Command{
	Name: "delete",
	Action: func(ctx context.Context, command *cli.Command) error {
		return nil
	},
}

이후 main.go에 커맨드들을 추가해 준다.

	rootCmd := &cli.Command{
		Commands: []*cli.Command{
			cmd.CreateCommand,
			cmd.DeleteCommand,
			cmd.InitCommand,
			cmd.KillCommand,
			cmd.StartCommand,
			cmd.StateCommand,
		},
	}

 

이제 원래의 동작을 현재의 구조에 맞게 옮길 것인데

진행하며 현재의 컨테이너 시작과 생성로직을 스펙에 맞게 분리해주어야 한다. 

이는 어떻게 동작해야 할까?

 

OCI Runtime Spec의 Operation을 보면 create와 start을 보자.

Create
create <container-id> <path-to-bundle>

This operation MUST generate an error if it is not provided a path to the bundle and the container ID to associate with the container. If the ID provided is not unique across all containers within the scope of the runtime, or is not valid in any other way, the implementation MUST generate an error and a new container MUST NOT be created. This operation MUST create a new container.

All of the properties configured in config.json except for process MUST be applied. process.args MUST NOT be applied until triggered by the start operation. The remaining process properties MAY be applied by this operation. If the runtime cannot apply a property as specified in the configuration, it MUST generate an error and a new container MUST NOT be created.

The runtime MAY validate config.json against this spec, either generically or with respect to the local system capabilities, before creating the container (step 2). Runtime callers who are interested in pre-create validation can run bundle-validation tools before invoking the create operation.

Any changes made to the config.json file after this operation will not have an effect on the container.

Start
start <container-id>

This operation MUST generate an error if it is not provided the container ID. Attempting to start a container that is not created MUST have no effect on the container and MUST generate an error. This operation MUST run the user-specified program as specified by process. This operation MUST generate an error if process was not set.

 

process.args는 start작업이 실행되기 전까지 적용되어서는 안 된다.

process.args는 문자열 배열로, IEEE 표준 execvp의 argv와 유사한 의미를 가진다.

 

또한 start 명령어는 process에 지정된 사용자 정의 프로그램을 실행해야 한다

 

즉, create과정에서 cgroup이나 기타 설정등.. 컨테이너에 대한 전반적인 init작업을 통해

사용자가 정의한 프로세스를 실행(process.args) 하기 전 단계까지를 수행한다.

 

이를 구현하기 위한 여러 방법이 있겠지만, 여기서는 시그널을 이용하겠다.

프로세스는 POSIX 표준에 의해 SIGSTOP으로 프로세스를 멈추고, SIGCONT를 이용해 시작할 것이다.

 

runc의 경우 이러한 시그널 방식이 아닌 FIFO Pipe 방식을 택한다. 이유야 여럿이지만 주요하게는

한창 이러한 처리가 개발되던 go ~1.6 버전에서 시그널 처리에 있어 레이스 컨디션이 발생하는 문제가 있었기 때문이다.

현재는 원자성을 지키도록 해결된 상태임으로 그대로 사용한다.

func Init() {
    ch := make(chan os.Signal)
    signal.Notify(ch, syscall.SIGCONT)
    <-ch

    fmt.Printf("Running: %v\n", os.Args[2:])

    cgroup.SetupCgroups()

    Must(syscall.Sethostname([]byte("container")))

    const rootfs = "/root/ubuntufs"
    Must(syscall.Chroot(rootfs))
    Must(os.Chdir("/"))

    Must(syscall.Mount("proc", "proc", "proc", 0, ""))
    Must(syscall.Mount("tmpfs", "mytemp", "tmpfs", 0, ""))

    defer Must(syscall.Unmount("proc", 0))
    defer Must(syscall.Unmount("mytemp", 0))

    if len(os.Args) < 3 {
       log.Fatal("Usage: containeruntime")
    }
    syscall.Exec(os.Args[2], os.Args[3:], os.Environ())
}

 

이제 container를 create 하여 SIGCONT 대기상태를 만든 이후

[hwyoon@rocky9 containeruntime]$ sudo ./containeruntime create /bin/bash
Running: [/bin/bash]

 

그리고 다른 터미널에서 프로세스를 향해 SIGCONT 시그널을 날려준다.

이는 kill 명령어를 통해 수행 가능하다.

[hwyoon@rocky9 containeruntime]$ sudo kill -SIGCONT 7244

 

그러면 멈춰있던 Init 프로세스가 동작하며 원래와 같은 동작을 수행한다.

[hwyoon@rocky9 containeruntime]# sudo ./containeruntime create /bin/bash
Running: [/bin/bash]
Running: [/bin/bash]
root@container:/# ls
bin   dev  home  lib32  libx32  mnt     opt   root  sbin  sys  usr
boot  etc  lib   lib64  media   mytemp  proc  run   srv   tmp  var

 

이를 start명령어로 빼기 위해서는 컨테이너의 상태(state)를 저장해야 한다.

이러한 상태 기반 동작은 다음 포스트에서 더 구현해 보자.

 

물론 현재의 SIGSTOP, SIGCONT구조등은 create와 start가 같은 터미널 세션에서 일어난다고

가정하기에 표준적인 컨테이너 런타임에 적용하기 결함이 많다.

때문에 이는 포스트가 진행되며 조금씩 변경될 예정이다. 

 

이외에도 main.go에 몰려있던 로직을 container와 linux패키지로 분리했다.

해당 포스트에서 다루지 않는 변경사항은 PR을 참조하자.

 

https://github.com/yoonhyunwoo/containeruntime/pull/1

 

feat: implement OCI runtime CLI structure with create/start separation by yoonhyunwoo · Pull Request #1 · yoonhyunwoo/containe

OCI Runtime Spec에 맞는 CLI 구조를 작성했다.

github.com

 

 

참고자료

https://github.com/golang/go/issues/14571

https://github.com/opencontainers/runc/pull/886

https://cli.urfave.org/v3/getting-started/

https://github.com/opencontainers/runtime-spec/blob/main/runtime.md

https://man7.org/linux/man-pages/man7/signal.7.html

동아제약에서 만든 얼박사다.

 

현재 1+1 진행 중이며 가격은 캔당 1,200원꼴이다.

 

원래 얼박사는 PC방에서 얼음 + 박카스 + 사이다를 조합해서

만드는 조합음료로 그 이름값이 높았는데, 박카스를 만드는 동아제약에서

해당 조합을 겨냥하여 만든 음료수다.

 

피시방에서 먹는 그 맛은 아니었고, 그냥 탄산 넣은 시원한 박카스였다.

썩 퀄리티 좋게 재현된 것 같진 않다.

'라이프' 카테고리의 다른 글

돈골약방 성수점  (4) 2025.07.31
미미램양꼬치  (4) 2025.07.25
고오스 초코케익  (0) 2025.07.24
싹수가 노란 점자블록  (1) 2025.07.22
작심 스터디카페 삼송점  (2) 2025.07.22

https://kko.kakao.com/KOWUTgOw7W

 

엄마손왕만두

경기 고양시 덕양구 삼송로 181

map.kakao.com

 

만두를 먹은지 오래되서 사왔다.

 

먼저 고기왕만두인데, 밀가루 맛이 너무 많이 난다.

모난 점 없이 스탠다드한 고기만두에 가깝긴 하지만

작은 육각형인 느낌이다.

 

다음은 김밥이다.

김밥은 은박지에 싼 김밥같은 맛이 난다.

기본적이면서 수상하게 고소한.. 알 사람들은 무슨 느낌인지 안다.

 

갈비만두는 아주 기본적으로 맛있었다.

만두집이라면 맛없게 하기가 더 힘든 음식 아닐까?

 

만두집인데 고기왕만두가 가장 별로였다.

다만, 고소한 김밥이 아주 만족스러웠다.

https://place.map.kakao.com/780523213

 

미미램양꼬치

서울 마포구 성미산로 196 상암빌딩 1층

place.map.kakao.com

 

연남동에 위치한 미미램양꼬치다. 가격은 3인 + 콜라 두 캔으로 약 72,000원이다.

꾸준히 인기있는 집으로 알고 있다.

 

카카오맵 AI 요약은 콜키지를 부과한다고 한다.
다음엔 와인이라도 하나 챙겨가야겠다.

언제 양꼬치집에서 콜키지를 내보겠는가

1층 중앙 원형 테이블에 앉았는데, 썩 편하지는 않았다.

내가 앉은자리만 에어컨인 도달하지 않아 찜통이었다.

 

미미램 양꼬치는 무한리필 양꼬치집인데, 테이블당 보조 메뉴 3개를 무료로 제공한다.

개중에서는 가지튀김이 썩 괜찮다.

 

이번에는 버섯튀김과 볶음밥 두 개를 시켰다.

한국인은 밥심이다.

 

양꼬치 집 중에서는 무난한 맛이다.

나는 허니돈육꼬치와 일반 양꼬치를 먹었는데, 

꼬치바이꼬치로 양념 차이가 심한 것 같다.

입에 넣을 때마다 새로웠다. 좋은 걸까?

 

양꼬치 양념으로는 기본으로 쯔란이 나온다.

출처 : https://www.kurly.com/goods/1000638379

 

이름이 생소하긴 하지만 양꼬치업계에선 De facto 한 시즈닝이다.

맛은 감칠맛이 조금 추가된 고춧가루 같은 맛이다.

이미 꼬치에 양념이 되어있기에 호불호가 갈릴 수 있지만 나는 좋아하는 편이다

놀랍게도 미나리과다. 

 

서울에서 가난한 자들이 먹을 수 있는 몇 안 되는 양꼬치다.

앞으로도 양꼬치가 생각나면 종종 올 것 같다.

 

양꼬리사진으로 마무리하겠다.

양꼬리

'라이프' 카테고리의 다른 글

돈골약방 성수점  (4) 2025.07.31
동아제약 얼박사  (1) 2025.07.27
고오스 초코케익  (0) 2025.07.24
싹수가 노란 점자블록  (1) 2025.07.22
작심 스터디카페 삼송점  (2) 2025.07.22

고오스 초코케익

편의점 기준 1,800원에 구매했다.

 

빵 포장이 앞뒤로 있어 빵이 두 개 들어있는 구성인가 싶었는데 가운데 초코크림을 둔 하나의 빵이었다.

어쩐지 먹다가 너무 뻑뻑해서 발견했다.

 

띠부띠부씰은 피카츄&윽우지가 나왔다.

 

윽우지는 식탐이 대단히 많아 적당한 크기의 모든 것을 삼켜버린다고 한다. 

이 네이밍 컨벤션으로는 지금 나는 고오스의초코케익&감압인 셈이다.

 

그 명성에 비해 크림이 소량 들어있어 아주 맛있게 먹지는 않았다.

한 번쯤은 먹어볼 만한 맛이다.

 

'라이프' 카테고리의 다른 글

동아제약 얼박사  (1) 2025.07.27
미미램양꼬치  (4) 2025.07.25
싹수가 노란 점자블록  (1) 2025.07.22
작심 스터디카페 삼송점  (2) 2025.07.22
BHC 홍제점  (4) 2025.07.21

컨테이너의 동작을 이해하기 위해 컨테이너 런타임을 만들어본다.

 

세상에 컨테이너 런타임이라고 알려진 소스들은 너무 많다.

containerd, cri-o, kata container, runc, crun, podman 등등..

 

그러나 이들은 같은 역할을 하는 도구가 아니다. 이 중 containerd, cri-o와 같은 런타임은

runc, crun 등을 사용하기도 한다. 같은 레벨의 소스가 아니라는 거다.

 

세간에서 불리는 컨테이너 런타임은 두 갈래로 나뉜다.

첫째. 실제로 컨테이너를 다루는 런타임.

둘째. 첫째의 런타임을 이용하여 컨테이너의 수명주기를 관리하는 런타임.

 

순서대로 OCI Runtime, CRI라는 표준을 따른다.

간략히 설명하자면 아래와 같다

 

OCI(Open Container Initiative) Runtime

싱글 바이너리로 산출되는 CLI프로그램으로, 실제로 컨테이너를 생성/삭제할 수 있다.

 

CRI(Container Runtime Interface)

Kubelet이 컨테이너 런타임과 소통하기 위해 정립한 인터페이스이나, 사실상 표준이다. (de fecto)

 

이 포스트 시리즈에서 만들 런타임은 둘이 섞여있는 형태일 것이다.

먼저 lizrice가 DockerCon 2017에서 발표한 containers-from-scratch 소스로부터 출발한다.

기본 코드블럭의 가독성이 좀 구린 것 같아서 carbon을 사용했다.
해당하는 커밋은 최하단에 Github 링크로 달아두었다.

 

대다수 컨테이너 런타임의 구조를 잘 담고 있는 소스다.

container create -> host 설정 -> /proc/self/exe 호출하며 프로세스 분리 -> 명령어 실행

runc, crun등 보통 컨테이너 런타임은 이런 구조를 차용한다.

 

하위호환을 잘 지키는 Go라지만 이는 6년 전 코드인지라 자잘하게 바꿔줘야 하는 부분이 있다.

크게 변경할 것은 없고 Cgroup V1을 사용하는 코드정도만 해설하겠다.

 

기존 코드의 Cgroup을 컨트롤하는 부분을 보자.

 

컨테이너를 생성시 아래와 같은 cgroup 세팅을 진행한다.

pids 서브시스템의 하위로 cgroup 인터페이스 파일을 생성하는 것 보아 CgroupsV1 구조임을 알 수 있다.

  • /sys/fs/cgroup/pids/liz/pids.max
  • /sys/fs/cgroup/pids/liz/notify_on_release
  • /sys/fs/cgroup/pids/liz/cgroup.procs

 

CgroupsV2에서는 이를 단일 계층(/sys/fs/cgroup)에서 직접 제어한다.

만약 pids 서브시스템을 제어하고 싶다면 다음과 같이 사용하면 된다.

echo "20" > /sys/fs/cgroup/liz/pids.max

이후 해당하는 그룹에 cgroup.procs 인터페이스 파일에 pid를 씀으로써 제한을 수행할 수 있다.

echo "20" > /sys/fs/cgroup/liz/cgroup.procs

 

이에 맞게 cg함수를 변경하고, 함수명등도 setupCgroups로 변경했다.

 

기타 변경사항이 적용된 코드는 다음과 같다.

 

다음 포스트에서 더 설명하겠지만 컨테이너 프로세스의 stdio, stdout, stderr을 현재 터미널에 연결하는 걸 볼 수 있다.

 

이는 OCI Runtime 스펙에 정의된 내용으로, 정확히는 아래와 같이 명시되어있다.

https://github.com/opencontainers/runtime-spec/blob/34a39b90707d979c56f834071c77ef60941d7688/runtime-linux.md

 

때문에 컨테이너 프로세스의 라이프사이클은 그 부모 프로세스(터미널)와 동일해진다.

이 말인즉슨 터미널이 종료되면 컨테이너 프로세스도 같이 종료된다는 말과 같다. 

그 때문에 CRI 스펙을 구현하는 고수준 컨테이너 런타임들은 터미널에게서 독립적으로 만드는 shim을 두어 이를 해결한다.

 

이런 부분은 살을 붙여갈 때 더 알아보도록 하고, 이를 실행시켜 보겠다.

이 프로그램을 실행시키기 위해서는 ubuntufs가 필요하다. 고수준 런타임을 사용할 때 사용하는 이미지의 실체가 바로 이것이다.

실제로는 bundle directory 외에도 여러 설정등이 명시되어 있다.

 

ubuntufs는 ubuntu:22.04 이미지에서 export 하여 사용한다.

sudo docker create --name temp-ubuntu ubuntu:22.04
sudo mkdir -p /root/ubuntufs
sudo docker export temp-ubuntu -o /tmp/ubuntu.tar
sudo tar -xf /tmp/ubuntu.tar -C /root/ubuntufs
sudo rm /tmp/ubuntu.tar
sudo docker rm temp-ubuntu
sudo /root/ubuntufs/mytemp

 

그리고 빌드 후 실행시켜 보면 정상적으로 동작한다.

[root@rocky9 containeruntime]# go build -o containeruntime .
[root@rocky9 containeruntime]# ls
containeruntime  go.mod  main.go  README.md
[root@rocky9 containeruntime]# ./containeruntime run /bin/bash
Running: [/bin/bash]
Running: [/bin/bash]
root@container:/# ls
bin   dev  home  lib32  libx32  mnt     opt   root  sbin  sys  usr
boot  etc  lib   lib64  media   mytemp  proc  run   srv   tmp  var
root@container:/# cat /etc/os-release 
PRETTY_NAME="Ubuntu 22.04.5 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.5 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy
root@container:/#
root@container:/# echo "on the container" 
on the container

 

간단한 컨테이너 실행 데모를 완료했으니 살을 붙이며 OCI Runtime을 구현할 예정이다.

bundle 및 config 설정, cli설정 등..

 

https://github.com/yoonhyunwoo/containeruntime/commit/aa99af3b6f102b6e99f04e7b35053c8bf6e003bc

 

Init commit · yoonhyunwoo/containeruntime@aa99af3

+ must(syscall.Mount("tmpfs", "mytemp", "tmpfs", 0, ""))

github.com

 

 

참고자료

https://github.com/kubernetes/community/blob/master/contributors/devel/sig-node/container-runtime-interface.md#design-docs-and-proposals

https://github.com/opencontainers/runtime-spec

https://github.com/lizrice/containers-from-scratch

https://www.youtube.com/watch?v=MHv6cWjvQjM&t=1316s

https://www.alibabacloud.com/help/en/alinux/support/differences-between-cgroup-v1-and-cgroup-v2

https://man7.org/linux/man-pages/man7/cgroups.7.html

요즘 케이팝 데몬 헌터스가 난리라고 해서 봤다.
 
케이팝 데몬 헌터스라더니 제작사는 소니다.
케이팝이 한일양국평화를 이루어내려나 보다.
땡큐 에스파.

이 아래로는 내용에 대한 스포가 있다.
 
세계관 배경은 과거부터 음악을 잘하던 여자 3인조가 악귀를 몰아내는 데몬 헌터로 활동하였고,
헌트릭스라는 3인조 걸그룹이 현대의 데몬 헌터로 활동한다.
 
그 컨셉답게 신명 나게 춤을 추며..!
주먹과 날붙이로 팬다.

이해가지 않는 설정이다.

 
헌트릭스의 적은 사자보이즈라는 5인조  보이 그룹으로
악귀들이 변장한 보이 그룹이다.
 
이 영화는 주인공 루미의 성장영화다.
루미는 반은 악귀고 반은 인간(헌터)이다.
사실 헤어스타일만 보면 그냥 악귀 같다.

흠..

마침 색깔도 비슷한 것이 죠죠의 리젠트컷이 생각난다. 

 
극이 진행되며 헌트릭스는 사자보이즈에게 서서히 입지를 빼앗기게 되고
루미는 정체성의 혼란으로 헌트릭스 멤버들은 자연스럽게 멀어진다.
 
결국 사자보이즈와의 최종결전에서는
진우(사자보이즈 리더)의 기만술로 무대에는 사실상 루미 혼자 서게 되고
변장한 악귀들의 팩트폭력으로 그만 루미는 반은 악귀 반은 인간에서
95% 악귀 5% 인간쯤으로 전락한다.
 
그렇게 헌트릭스는 나락으로 빠지고
악귀들의 왕인 귀마를 부르기 위한 최종스텝을 밟으러 남산타워로 나아간다.
 
여기까지 보면 사자보이즈의 유쾌 상쾌 통쾌 언더독 쿠데타 성공극으로 보이겠지만 그렇지 않다.
 
최후의 남산타워 공연에서 자신을 그대로 받아들이기로 한 루미의 원맨쑈를 시작으로
헌트릭스 멤버들이 하나 되어 악귀들을 무찌른다.
 
그렇다. 루미는 갑자기 똑똑이가 되어 나타난다.
납득 가는 전개는 아니었지만 그것은 중요하지 않다.
 
왜냐고? 이 영화의 장점은 스토리가 아니기 때문이다.
내가 느낀 장점은 두 가지 정도다.
 
첫째. 청각적 즐거움. 작품 이름부터가 케이팝 데몬 헌터스로 아이돌대전인 만큼 OST가 많이 나오는데
특유의 오글거림 없이 지극히 자연스럽고 좋다. 특히 케이팝 특유의 한영혼용을 잘 섞었다.
 
둘째. 시각적 즐거움. 아이돌 캐릭터들의 무-빙이나 케이팝 특유의 카메라 워킹 연출등은 놀라웠다.
이러한 연출들이 더 몰입감 있게 영화를 볼 수 있게 해 준 것 같다.
 

총평

오랜만에 나온 웰메이드 애니메이션 영화인 것 같다.
 
원래 애니메이션 영화를 좋아하지만 근래의 픽사는 너무 자기 복제적 영화만 나오는 것 같다.
일례로 최근 영화들을 나열해 보자.
 
엘리오, 엘리멘탈, 버즈 라이트이어, 메이의 새빨간 비밀, 루카, 소울, 온워드:단 하루의 기적 등등..
 
개중에서도 루카, 온워드, 소울, 루카, 메이의 새빨간 비밀로 이어지는 숨 막히는 라인은
내가 4편의 영화를 본 건지 온워드, 영혼 온워드, 물고기 온워드, 새빨간 온워드를 본건지 모를 정도로 비슷했다.
 
물론 케데헌도 꽤 비슷한 양상이다. 정체성에 혼란을 느끼는 주인공,
새로운 세계로의 진입 혹은 시련, 자신을 있는 그대로 인정하면서 엔딩..
 
그러나 앞선 픽사의 영화들은 주력이 스토리와 감동이었고
케데헌의 경우 훌륭한 사운드와 영상미를 내세웠기에 즐겁게 볼 수 있었다.
 
한번쯤 볼만한 영화다.
추천드린다.

'라이프 > 리뷰' 카테고리의 다른 글

한로로 단독콘서트 - 자몽살구클럽  (0) 2025.12.30
F1 더 무비  (5) 2025.08.05

테라폼(Terraform)은 de facto에 가까운
IaC(Infrastructure as Code) 도구다.
 
그중에서도 수많은 Provider들은 테라폼으로
마법 같은 인프라 정의와 배포를 가능하게 한다.
 
프로바이더들은 비교적 데이터 Input과 Output이
명확하게 정의되어 다음과 같은 데이터 흐름을 가진다.

HCL(HashiCorp Configuration Language) -> Expand -> Flatten -> State

 
 
아주 직관적인 구조라고 생각한다. HCL과 State의 경우에는 인프라를 정의하는 언어와 현재 상태다.
 
그중에 생소한 Expand와 Flatten이 있다.
본 글에서는 대부분 terraform-provider-aws를 기준으로 설명한다.
 
Expand는 HCL을 프로바이더에 요청 가능한 구조체로 변환해 주는 작업,
Flatten은 프로바이더에서 응답한 Response를 State로 변환해 주는 작업이다.
VPC를 생성하는 경우를 보자. HCL의 경우에는 아래와 같다.

resource "aws_vpc" "example" {
  cidr_block = "10.0.0.0/16"
}

 
이것이 Expand 되면 다음과 같은 형태로 변한다.

type CreateVpcInput struct {
	AmazonProvidedIpv6CidrBlock *bool
	CidrBlock *string
	DryRun *bool
	InstanceTenancy types.Tenancy
	Ipv4IpamPoolId *string
	Ipv4NetmaskLength *int32
	Ipv6CidrBlock *string
	Ipv6CidrBlockNetworkBorderGroup *string
	Ipv6IpamPoolId *string
	Ipv6NetmaskLength *int32
	Ipv6Pool *string
	TagSpecifications []types.TagSpecification
}

 
이를 이용한 리서스 생성 요청 이후에 flatten을 통해 terraform state로 사용 가능한 형태로 만든다.
원래 상태로 돌아온다고 보면 된다.

resource "aws_vpc" "example" {
  cidr_block = "10.0.0.0/16"
}

 
여기서 원래 상태는 편의를 위해 HCL의 문법으로 표현하고 있지만 실제로는
Resource, Schema로 대표되는 테라폼 프로바이더에서 사용하는 데이터 타입이다.
 
짧은 예시를 보자

	return &schema.Resource{
		CreateWithoutTimeout: resourceRegistryPolicyPut,
		ReadWithoutTimeout:   resourceRegistryPolicyRead,
		UpdateWithoutTimeout: resourceRegistryPolicyPut,
		DeleteWithoutTimeout: resourceRegistryPolicyDelete,

		Importer: &schema.ResourceImporter{
			StateContext: schema.ImportStatePassthroughContext,
		},

		Schema: map[string]*schema.Schema{
			names.AttrPolicy: {
				Type:                  schema.TypeString,
				Required:              true,
				DiffSuppressFunc:      verify.SuppressEquivalentPolicyDiffs,
				DiffSuppressOnRefresh: true,
				ValidateFunc:          validation.StringIsJSON,
				StateFunc: func(v any) string {
					json, _ := structure.NormalizeJsonString(v)
					return json
				},
			},
			"registry_id": {
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}

 
map[string]을 기반으로 되어있고 한 스키마당 Required부터 시작해서
4~5개의 속성을 지정해 주어야 하므로 자동완성도 지원하지 않고
코드 길이가 아주 길어지기 때문에 개인적으로 불만이 많다.
 
위와 같은 구조는 HCL로는 다음과 같이 표현된다.

resource "aws_ecr_registry_policy" "example" {
  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Sid    = "testpolicy",
        Effect = "Allow",
        Principal = {
          "AWS" : "arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:root"
        },
        Action = [
          "ecr:ReplicateImage"
        ],
        Resource = [
          "arn:${data.aws_partition.current.partition}:ecr:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:repository/*"
        ]
      }
    ]
  })
}

 
직관적으로 string을 받는 policy와 registry_id가 있다.
policy에는 jsonencode된 iam policy 등이 들어가게 된다. 
 
registry_id는 computed가 True로 되어있다.
이것은 프로바이더가 값을 설정할 것이라는 의미로 리소스에서는 정의할 수 없다.
이렇게 정의된 속성은 어트리뷰트가 된다.
 
실제 registry_id에 대한 설명을 보면 더 이해가 쉬울 것이다.
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecr_registry_policy#registry_id-1
 
기본적인 개념은 이게 끝이다.
언제라도 없는 AWS리소스를 추가할 수 있다.
 
다음과 같이 접근하라.
1. 대상의 리소스와 스키마 정의
2. expand 함수와 API 요청 작성
3. flatten 함수 작성
 
물론 아예 새로운 리소스를 추가할 땐
이런저런 절차가 더 들어간다.
기회가 되면 다뤄보도록 하겠다.
 
참고 문서
https://hashicorp.github.io/terraform-provider-aws/data-handling-and-conversion/
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc
https://developer.hashicorp.com/terraform/plugin/framework/handling-data/schemas
 

playwright java의 경우 다음과 같이 브라우저를 설치한다.

mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps chromium"

 

그런데 CI(continuous integration)에서 위와 같이 종속성을 설치할 시

CI의 기본 노드 코어를 임시/캐시 디렉터리에 복사하여 사용한다

이러한 동작은 여러 애로사항을 불러일으킨다.

 

CI워커의 수명 주기가 짧은 경우 캐싱 미스

임시 디렉터리에 쓸 수 없는 보안 요구사항

서명된 exe만 실행 가능한 보안 요구사항 등..

 

이런 애로사항이 한두 명 있던 게 아니었는지 이 PR에서 playwright-java에 PLAYWRIGHT_NODEJS_PATH
환경변수를 통해 node.js 경로을 지정하여 의존하는 버전을 지정할 수 있게 되었다.

 

그러나 이를 Docs에 적어주지는 않았다.
오픈소스의 고질적인 문제다.

 

이를 문서에 추가하는 간단한 PR로 제출했다.

https://github.com/microsoft/playwright/pull/36747/files

 

docs: add PLAYWRIGHT_NODEJS_PATH usage instructions by yoonhyunwoo · Pull Request #36747 · microsoft/playwright

Document the PLAYWRIGHT_NODEJS_PATH environment variable added in PR #16518

github.com

 

오픈소스 생태계의 낙숫물을 받아먹고 있기에

간단히 추가할 만한 문서화 정도는 도와주도록 하자.

 

참고 자료

https://github.com/microsoft/playwright-java/pull/1030/files

 

'인프라' 카테고리의 다른 글

인텔 BMC 시스템 Debug Log Password  (3) 2025.08.05

+ Recent posts