멀티 컨테이너 Machines
Fly Machines는 containers 배열을 사용해 가상 머신 하나에서 여러 컨테이너를 실행할 수 있어요. 이 기능은 애플리케이션 서버, 로그 전송기, 메트릭 수집기, 사이드카 같은 서비스를 한 곳에 배치하는 데 유용해요. 이 페이지에서는 컨테이너 정의, 의존성, 헬스 체크, 그리고 배포 방법을 설명해요.
출처: 문서
본문
Fly Machines는 containers 배열을 사용해 가상 머신 하나에서 여러 컨테이너를 실행할 수 있어요. 이 기능은 현재 Machines API를 통해 사용할 수 있으며 이니트 시스템인 Pilot을 사용해야 해요. 이 기능은 애플리케이션 서버, 로그 전송기, 메트릭 수집기, 사이드카 같은 서비스를 한 곳에 배치하는 데 유용해요.
흔한 사이드카 사용 사례
사이드카는 지원 서비스를 애플리케이션 옆에 배치하는 강력한 방법이에요. 이 패턴은 AI 에이전트, 개발 환경, SaaS 대시보드 같은 멀티 테넌트 또는 사용자별 아키텍처로 만들어진 앱에 특히 유용해요. 실용적인 사용 예는 다음과 같아요:
- 메트릭과 로그: Prometheus exporter나 Vector 같은 로그 전송기를 실행해 그 로직을 앱에 번들하지 않고도 애플리케이션 로그나 메트릭을 전달할 수 있어요.
- 시크릿 에이전트: 사이드카를 사용해 Vault나 클라우드 메타데이터 서비스 같은 제공자에서 시크릿을 가져와요. 이렇게 하면 앱 컨테이너를 가볍고 안전하게 유지할 수 있어요.
- 부하 완화 (Load Smoothing): nginx나 Envoy를 리버스 프록시로 사용해 속도 제한, 재시도, 우아한 오류 처리를 해요. 트래픽 폭주나 외부 API 불안정을 처리하는 데 아주 좋아요.
- 스토리지 또는 동기화 계층: LiteFS나 파일 동기화 데몬 같은 사이드카로 로컬 상태를 마운트하고 관리해요. 영구 로직을 앱 로직과 분리해 둘 수 있어요.
- AI 에이전트 또는 워커: 에이전트 기반이나 대기열 기반 워크로드에서 별도 컨테이너에 워커나 스케줄러를 띄우고
depends_on으로 조정할 수 있어요.
이 패턴들은 Pilot과 containers 배열로 Fly Machines에서 프로덕션 시스템을 구축한 고객들의 사례와 일치해요. 각 컨테이너는 자체 격리된 프로세스 트리에서 실행되며, Pilot이 시작 순서와 헬스 상태가 존중되도록 보장해요. 컨테이너는 같은 커널과 VM을 공유하지만 프로세스와 파일 시스템 수준에서는 격리돼요.
컨테이너 정의
API로 Machine을 만들 때 containers 배열을 지정할 수 있어요. 각 항목에는 ContainerConfig가 들어 있어요. 각 컨테이너는 자체 이미지, 환경 변수, 헬스 체크, 시작 명령, 연결된 파일, 의존성을 가질 수 있어요.
flyd(우리의 사내 오케스트레이터)는 이미지를 가져와 컨테이너를 실행하고 생명주기를 관리하는 이니트 시스템인 Pilot이 접근할 수 있게 해 줘요. Pilot은 컨테이너 시작 조건을 사용해 의존성 그래프를 만들고 그에 따라 컨테이너를 실행해요.
예시 설정 (Example Configuration)
{
"containers": [
{
"name": "my-app",
"image": "registry.fly.io/my-app:latest",
"env": {
"PORT": "8080"
},
"healthchecks": [
{
"http": {
"port": 8080,
"method": "GET",
"path": "/health",
"scheme": "http"
},
"interval": 10,
"timeout": 5,
"grace_period": 5,
"success_threshold": 2,
"failure_threshold": 3
}
]
},
{
"name": "log-shipper",
"image": "registry.fly.io/log-shipper:latest",
"env": {
"LOG_LEVEL": "info"
},
"depends_on": [
{
"name": "my-app",
"condition": "healthy"
}
]
}
]
}
이 설정에서 log-shipper 컨테이너는 my-app 컨테이너가 healthy가 된 후에 시작해요.
컨테이너 의존성
depends_on 필드를 사용해 컨테이너 간 의존성을 정의할 수 있어요. 지원되는 조건은 다음과 같아요:
"depends_on": [
{
"name": "sidecar1",
"condition": "healthy"
}
]
condition의 유효한 값:
healthy: 의존 컨테이너가 헬스 체크를 통과할 때까지 기다려요.started: 의존 컨테이너가 시작되자마자 시작돼요.exited_successfully: 의존 컨테이너가 성공 코드로 종료될 때까지 기다려요.
내부적으로 Pilot은 이러한 의존성을 방향 그래프로 모델링하고 그것을 탐색해 올바른 컨테이너 시작 순서를 조율해요.
헬스 체크
각 컨테이너는 자체 헬스 체크를 지정해요. 시스템은 다양한 유형의 헬스 체크를 지원해요:
TCP 헬스 체크: 포트가 연결을 받는지 확인해요
"tcp": {
"port": 6379
}
HTTP 헬스 체크: 엔드포인트에 HTTP 요청을 해요
"http": {
"port": 80,
"method": "GET",
"path": "/",
"scheme": "http"
}
Exec 헬스 체크: 컨테이너 안에서 명령을 실행해요
"exec": {
"command": ["sh", "-c", "pg_isready -U postgres"]
}
헬스 체크 설정 옵션
name: string - 헬스 체크의 고유 식별자interval: int - 체크를 얼마나 자주 실행할지(초 단위)grace_period: int - 컨테이너 시작 후 체크가 시작되기 전까지 기다리는 시간(초)timeout: int - 체크 완료를 기다리는 최대 시간(초)success_threshold: int - 상태를 healthy로 바꾸는 데 필요한 연속 성공 체크 횟수failure_threshold: int - unhealthy로 표시하기 전의 연속 실패 체크 횟수
보안 고려 사항
Machine의 컨테이너는 같은 커널과 VM을 공유하지만 프로세스와 파일 시스템 수준에서는 격리돼요. 한 컨테이너의 장애가 다른 컨테이너를 직접 충돌시키지는 않지만, VM 간 격리와 같은 수준의 격리를 제공하지는 않아요.
멀티 컨테이너 Machines 배포
Fly.io에서 멀티 컨테이너 머신을 배포하는 방법은 여러 가지예요. 워크플로우에 가장 잘 맞는 방법을 선택하세요:
Docker Compose 사용하기
이미 Docker Compose 설정이 있다면 설정을 다시 쓰지 않고도 Fly Machines에 배포할 수 있어요. Fly는 Compose 서비스를 단일 Machine 안의 컨테이너로 빌드하고 실행해요.
요구 사항
flyctl v0.3.152 이상이 필요해요:
fly version
버전이 낮다면 fly version update로 업데이트하세요.
설정
fly.toml에 [build.compose] 섹션을 추가하세요:
[build.compose]
Fly는 표준 조회 순서에 따라 Compose 파일을 자동 감지해요: compose.yaml, compose.yml, docker-compose.yaml, docker-compose.yml. 파일 이름이 다르다면 지정하세요:
[build.compose]
file = "docker-compose.prod.yml"
트래픽 라우팅
fly.toml의 [[services]] 또는 [http_service] 블록에서 internal_port를 트래픽을 받아야 하는 컨테이너가 노출하는 포트와 일치하도록 설정하세요. Fly 프록시의 인바운드 요청은 하나의 컨테이너만 처리해요.
[http_service]
internal_port = 8080
force_https = true
예시
Redis 사이드카가 있는 웹 서비스로, 둘 다 같은 Machine에서 실행돼요. fly deploy는 프로젝트 디렉터리의 Dockerfile에서 web 서비스를 빌드하고, redis는 미리 빌드된 사이드카로 실행돼요.
Dockerfile:
FROM nginxdemos/hello:plain-text
compose.yml:
services:
web:
build: .
ports:
- "80:80"
redis:
image: redis:latest
fly.toml:
app = "my-compose-app"
primary_region = "ord"
[build.compose]
[http_service]
internal_port = 80
force_https = true
auto_stop_machines = "stop"
auto_start_machines = true
[[vm]]
size = "shared-cpu-1x"
memory = "512mb"
다음으로 배포하세요:
fly deploy
web 컨테이너는 80번 포트에서 인바운드 트래픽을 처리하고, redis 컨테이너는 사이드카로 옆에서 실행돼요. 두 컨테이너가 Machine 안에서 같은 네트워크 네임스페이스를 공유하므로 애플리케이션 코드가 localhost:6379로 Redis에 접근할 수 있어요.
경고: Fly는 Compose 파일의
volumes:선언을 무시하고 배포 시Warning: Could not read volume file...을 출력해요. 배포 자체는 성공하지만 컨테이너가 쓰는 모든 데이터는 재시작이나 재배포 시 지워지는 임시 오버레이에 저장돼요. 영구 스토리지가 필요하다면 Fly 볼륨을 연결하고fly.toml의[mounts]로 마운트하세요.
제한 사항
- 정확히 하나의 빌드 가능한 서비스.
fly deploy는 Compose 파일에서 정확히 하나의 서비스가build를 지정해야 해요. 다른 모든 서비스는 미리 빌드된 이미지를 사용해야 해요. - 시크릿은 전역적이에요.
fly secrets로 설정한 시크릿은 Machine의 모든 컨테이너에서 사용할 수 있어요. 시크릿을 단일 서비스로 한정할 수 없어요. - 환경 변수 충돌. Fly는 런타임 환경 변수(예:
FLY_APP_NAME,PRIMARY_REGION등)를 모든 컨테이너에 주입해요. 이것들이 Compose 파일environment블록에서 정의한 값을 덮어쓸 수 있어요.
Machines API 사용하기
Machines API에 POST 요청을 보내 멀티 컨테이너 Machines를 만들 수 있어요. 예를 들어 curl을 사용하면:
curl -X POST "https://api.machines.dev/v1/apps/<your-app-name>/machines" \
-H "Authorization: Bearer ***" \
-H "Content-Type: application/json" \
-d @api-config.json
여기서 api-config.json 파일은 Machine 생성 요청 본문을 담고 있어요. 다음 섹션의 flyctl 예시와 달리, API는 JSON 본문에 containers 배열을 중첩한 config 래퍼를 기대해요:
{
"config": {
"containers": [...],
...
}
}
config 객체는 guest와 services 같은 다른 Machine 필드도 포함할 수 있어요.
전체 설정 옵션은 Machines API 문서를 참고하세요.
fly machine run 사용하기
JSON 설정 파일(예: cli-config.json)을 fly machine run에 전달해 멀티 컨테이너 Machine을 만들어요:
fly machine run --machine-config cli-config.json
이 파일은 추가 Machine 설정과 함께 루트 레벨에 containers를 정의해야 해요:
{
"containers": [...],
...
}
명령줄에서도 Machine 설정 옵션을 전달할 수 있어요:
fly machine run --machine-config cli-config.json \
--autostart=true --autostop=suspend \
--port 80:8080/tcp:http --port 443:8080/tcp:http:tls \
--vm-cpu-kind shared --vm-cpus 1 --vm-memory 256
참고:
--machine-config를 사용할 때 JSON 파일에는 최소한 하나의image를 지정하는 컨테이너가 포함된containers배열이 있어야 해요.
fly launch와 fly deploy 사용하기
flyctl v0.3.147부터 fly deploy로 여러 컨테이너를 실행할 수 있어요. 이 예시는 nginx 컨테이너를 레이트 리미터로 사용하고 사용자 정의 앱 컨테이너와 함께 사용하는 방법을 보여줘요.
1. cli-config.json 생성
{
"containers": [
{
"name": "nginx",
"image": "nginx:latest",
"files": [
{
"guest_path": "/etc/nginx/conf.d/default.conf",
"local_path": "nginx.conf"
}
],
"depends_on": [
{
"name": "echo",
"condition": "healthy"
}
]
},
{
"name": "echo",
"image": "ealen/echo-server",
"healthchecks": [
{
"exec": {
"command": ["wget", "-q", "--spider", "http://localhost"]
}
}
]
}
]
}
2. fly.toml 업데이트
참고: 이 설정을 사용하려면 flyctl 버전 0.3.147 이상이 필요해요
[experimental]
machine_config = 'cli-config.json'
container = 'echo'
[http_service]
internal_port = 8080
container 값은 fly deploy가 빌드한 여러분의 앱 이미지로 대체될 컨테이너를 결정해요.
3. fly deploy 실행
fly deploy
이 명령은 echo 컨테이너를 빌드하고 대체하며, 두 컨테이너를 같은 머신에서 실행하고, nginx를 통해 트래픽을 구성해요.
machine_config를 삼중 따옴표를 사용해 fly.toml에 인라인으로 넣을 수도 있어요. 문자열의 첫 문자는 {여야 해요:
machine_config = '''{
"containers": [
…
]
}'''
예시 설정 (Example Configurations)
시작점으로 사용할 수 있는 실용적인 멀티 컨테이너 설정 예시가 몇 가지 있어요:
레이트 리미터 사이드카
과도한 트래픽을 완화하기 위해 nginx 컨테이너를 사이드카로 실행해 ngx_http_limit_req_module로 초당 요청 수를 제한할 수 있어요. 아래는 레이트 리미팅을 활성화하는 단순화된 컨테이너 설정이에요:
{
"containers": [
{
"name": "nginx",
"image": "nginx:latest",
"files": [
{
"guest_path": "/etc/nginx/conf.d/default.conf",
"raw_value": "bGltaXRfcmVxX3pvbmUgJGJpbmFyeV9yZW1vdGVfYWRkciB6b25lPW15X3pvbmU6MTBtIHJhdGU9MXIvczsKCnNlcnZlciB7CiAgbGlzdGVuIDgwODA7CgogIGxvY2F0aW9uIC8gewogICAgbGltaXRfcmVxIHpvbmU9bXlfem9uZTsKICAgIHByb3h5X3Bhc3MgaHR0cDovL2xvY2FsaG9zdDo4MDsKICB9Cn0="
}
]
},
{
"name": "echo",
"image": "ealen/echo-server"
}
]
}
컨테이너에 파일을 연결할 때 raw_value 필드는 base64로 인코딩된 내용을 담아야 해요. 위 JSON에 표시된 nginx 설정은 다음 설정과 대응돼요:
limit_req_zone $binary_remote_addr zone=my_zone:10m rate=1r/s;
server {
listen 8080;
location / {
limit_req zone=my_zone;
proxy_pass http://localhost:80;
}
}
컨테이너가 시작되면 Fly가 이 값을 디코드해 원래 설정을 지정된 guest_path에 써요.
이 설정을 완성하려면 다음도 포함해야 해요:
- Fly Proxy용 내부 포트 8080을 노출하는
services블록 - CPU와 메모리 같은 리소스를 지정하는
guest블록 - 선택적으로, nginx가 앱을 기다리게 하려면
depends_on필드나 헬스 체크
헬스 체크가 있는 LiteFS 사이드카
LiteFS를 사이드카로 실행하면서 올바르게 동작하는지 확인하는 헬스 체크를 구성할 수도 있어요:
{
"containers": [
{
"name": "litefs",
"image": "flyio/litefs:latest",
"env": {
"LITEFS_DIR": "/data",
"LITEFS_CONFIG": "/etc/litefs.yml"
},
"healthchecks": [
{
"http": {
"port": 8080,
"method": "GET",
"path": "/info",
"scheme": "http"
},
"grace_period": 500,
"interval": 1000,
"timeout": 1000,
"success_threshold": 1,
"failure_threshold": 5
}
]
}
]
}
참고: 컨테이너 설정 옵션에 대한 자세한 내용은 Machines API 문서를 참고하세요.