- Podman은 데몬이 없기 때문에 서버 부팅 뒤 컨테이너를 안정적으로 올릴 실행 주체가 필요하며, Quadlet은 이를 systemd 서비스 파일 방식으로 해결함
- 기존
podman generate systemd는 컨테이너 생성과 서비스 파일 생성을 따로 처리해야 했고, 생성 파일을 바꿀 때마다 수동 편집 부담이 생겼음 - Quadlet은
~/.config/containers/systemd의.container파일에[Container],[Service],[Install]설정을 함께 두어 Podman 옵션과 systemd 동작을 한곳에서 관리함 - rootless Podman에서는 부팅 자동 시작을 위해
WantedBy=default.target와 linger 활성화가 필요하며,multi-user.target은 사용자 모드에 정의되어 있지 않음 AutoUpdate=registry, systemd 의존성, 디렉터리별 파일 그룹화,podlet변환 도구까지 활용하면 Quadlet은 rootless·daemonless Podman 운영에 잘 맞음
왜 Quadlet이 필요한가
- Quadlet은 Podman 컨테이너를 systemd 서비스처럼 실행하게 해 주는 방식임
- 컨테이너를 백그라운드에서 실행할 수 있음
- 서버 재부팅 뒤 컨테이너를 자동으로 시작할 수 있음
- Podman 컨테이너를 systemd 아래에서 실행하는 방식 자체는 새롭지 않음
- 기존에는
podman generate systemd명령을 사용했음 - 이 명령은 이제 Quadlet으로 마이그레이션하라는 사용 중단 경고를 표시함
- 기존에는
- Podman은 daemonless 구조이므로, 데몬 없이 컨테이너를 시작해 줄 실행 주체가 필요함
기존 podman generate systemd 방식의 불편함
- 예전 방식은 먼저
podman create로 컨테이너를 만들어야 했음- 예시 컨테이너는
docker.io/library/postgres:16이미지를 사용함 -p 5432:5432, 볼륨 마운트,POSTGRES_PASSWORD환경 변수,io.containers.autoupdate=registry라벨을 포함함
- 예시 컨테이너는
- 이후
podman generate systemd test-db -fn --new를 실행해container-test-db.service파일을 생성했음 - 생성된 서비스 파일은
~/.config/systemd/user에 배치하고 다음 명령으로 활성화·시작했음systemctl --user enable --now container-test-db
- 이 흐름은 컨테이너 생성, 서비스 파일 생성, 필요한 경우 파일 이동, 서비스 활성화를 반복하게 만듦
- 컨테이너 생성 명령이 길어지면 나중에 다시 실행하기 위해 셸 스크립트를 별도로 관리해야 했음
- 생성된 systemd 서비스 파일을 커스터마이즈하려면 매번 다시 수동 수정해야 했음
Quadlet 파일 구성
- Quadlet 방식에서는
~/.config/containers/systemd디렉터리를 만들고 그 안에.container파일을 둠 - 예시
test-db.container파일은 다음 요소를 포함함[Container]Image=docker.io/library/postgres:16AutoUpdate=registryPublishPort=5432:5432Volume=%h/volumes/test-db:/var/lib/postgresql/data:ZEnvironment=POSTGRES_PASSWORD=CHANGE_ME
[Service]Restart=always
[Install]WantedBy=default.target
.container파일은 일반 systemd 서비스 파일이지만, 특별한[Container]섹션을 포함함[Container]의 많은 옵션은podman create에서 쓰는 명령줄 옵션에 대응함Image: 사용할 이미지와 태그AutoUpdate=registry:--label "io.containers.autoupdate=registry"에 대응PublishPort:-p에 대응Volume:-v에 대응Environment:-e에 대응
- 사용자 홈 디렉터리에는
~대신 systemd 지정자인%h를 사용해야 함 [Service]의Restart=always는 수동으로 중지하지 않는 한 컨테이너를 항상 재시작하게 함[Install]의WantedBy=default.target는 컨테이너가 부팅 시 자동 시작되게 함
rootless Podman에서 필요한 systemd 설정
- rootless 컨테이너에서는
multi-user.target이 아니라default.target을 사용해야 함multi-user.target은 systemd 사용자 모드에 정의되어 있지 않음systemctl --user status multi-user.target로 확인할 수 있음- 시스템 모드에서는
systemctl status multi-user.target로 확인 가능함
- systemd 사용자 서비스로 컨테이너를 실행하므로, 사용자가 로그인하지 않아도 컨테이너가 시작되게 하려면 linger를 활성화해야 함
loginctl enable-linger
- 서버 재부팅 후 자동 시작이 필요하다면 linger 활성화가 필수임
- 새 서비스 파일을 systemd가 발견하게 하려면 다음 명령을 실행함
systemctl --user daemon-reload
- 컨테이너 시작과 상태 확인은 systemd와 Podman 양쪽에서 할 수 있음
systemctl --user start test-dbsystemctl --user status test-dbpodman ps
- 기본 컨테이너 이름은 서비스 파일 이름 앞에
systemd-가 붙은 형태임test-db.container의 기본 컨테이너 이름은systemd-test-db- 충돌을 피하기 위한 이름 규칙임
[Container]섹션의ContainerName옵션으로 직접 지정할 수 있음
Quadlet의 운영상 장점
- Quadlet은 컨테이너 서비스 설정을 파일 하나로 관리하게 해 줌
- 기존 방식처럼 서비스 파일을 생성하는 스크립트와 생성된 서비스 파일을 따로 관리하지 않아도 됨
- systemd의
[Unit]과[Service]섹션에서 가능한 옵션을 사용할 수 있음- 예를 들어 컨테이너 시작 전 실행할 명령을
StartExecPre로 지정할 수 있음 - 생성된 파일을 나중에 수동으로 고치는 과정이 줄어듦
- 예를 들어 컨테이너 시작 전 실행할 명령을
- 셸 스크립트를 작성하고 디버깅하는 대신 구성 파일 중심으로 컨테이너를 운영할 수 있음
- 컨테이너 간 의존성을 systemd 방식으로 표현하기 쉬움
컨테이너 간 의존성 표현
- 앱 컨테이너가 데이터베이스 컨테이너에 의존한다면 systemd의
[Unit]섹션으로 관계를 지정할 수 있음 - OxiTraffic 컨테이너는
test-db컨테이너에 의존하는 예시임[Unit]에Requires=test-db.service를 설정해 데이터베이스가 시작될 때만 앱이 시작되게 함After=test-db.service를 설정해 두 컨테이너가 병렬로 시작되지 않게 함
- 참조할 때는
.container파일명이 아니라 서비스 이름을 사용함test-db.container: 파일 이름test-db.service: 서비스 이름systemd-test-db: 기본 컨테이너 이름
- 앱이 데이터베이스와 통신하려면 두 컨테이너 모두
[Container]섹션에Network옵션을 추가해야 하지만, 네트워킹은 다루지 않음
여러 파일과 그룹화
- Quadlet은 컨테이너마다 별도
.container파일을 두는 방식으로 구성할 수 있음 - 멀티 컨테이너 앱을 하나의 Docker Compose 파일에 모두 넣는 방식보다, 컨테이너별 파일이 인지 부담을 줄일 수 있음
- Docker Compose 파일이 수백 줄과 수십 개 컨테이너를 포함하면 유지보수가 어려워질 수 있음
- Mailcow의 Docker Compose 파일이 큰 Compose 파일의 예시임
- Docker Compose도 여러 파일로 나누는 기능을 지원함
- Quadlet은
~/.config/containers/systemd안의 디렉터리에 unit 파일을 둘 수 있음- 예시에서는
oxitraffic디렉터리를 만들고 앱과 데이터베이스 파일을 함께 둘 수 있음
- 예시에서는
이미지 업데이트
AutoUpdate=registry를 설정하면podman auto-update로 이미지 업데이트를 확인할 수 있음- 레지스트리에 사용 중인 태그와 호환되는 새 이미지가 있으면 Podman이 이미지를 pull하고 컨테이너를 재시작함
- Docker에서는 이 용도로 Watchtower 같은 도구가 필요할 수 있지만, Podman은 해당 기능을 기본 제공함
latest같은 태그는 위험할 수 있음- OxiTraffic에서
latest가 다음 버전의 breaking change를 포함할 수 있음 - PostgreSQL에서
latest를 쓰면 새 major 버전으로 올라갈 수 있고, PostgreSQL major 업그레이드에는 항상 수동 마이그레이션이 필요함
- OxiTraffic에서
- 운영에서는 breaking change로 이어지지 않는 태그를 사용해야 함
podman auto-update를 수동으로 며칠마다 실행해 무엇이 업데이트됐는지 보고, 이후 컨테이너가 정상인지 확인하는 방식도 가능함
podman-compose와 마이그레이션 도구
podman-compose는 Compose 파일을 Podman으로 실행하는 Python 스크립트임- 장기적인 Docker Compose 대안으로 보기 어려운 이유는 다음과 같음
- Compose 명세와 Podman·systemd 사이의 번역 계층이며, systemd의 모든 기능을 쓰게 해 주지 않음
- 공식 Podman 프로젝트는 Rust나 Go 같은 컴파일 언어로 작성됨
- 마지막 커밋이 5개월 전이라 활발히 유지보수되지 않음
- Quadlet은 Podman의 rootless·daemonless 설계와 더 잘 맞음
- Compose 파일에서 Quadlet을 시도하려면 podlet을 사용할 수 있음
- Podman 명령이나 Docker Compose 파일에서 Quadlet 파일을 만들 수 있는 Rust 도구임
더 읽을 자료
- Quadlet을 더 깊게 이해하려면
podman-systemd.unitman page가 유용함 - Quadlet은 컨테이너뿐 아니라 pod, network, volume도 관리할 수 있음
- systemd unit 파일 작성이 익숙하지 않다면
systemd.unit과systemd.serviceman page를 볼 수 있음 - 다른 관점과 두 번째 예시는 blog.while-true-do의 유사 글에서 확인할 수 있음