본문으로 바로가기 본문 바로가기
WIKI 기술 지식 베이스

멀티 컨테이너 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 문서를 참고하세요.

더 알아보기 (Learn more)