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

flyctl로 Machines 실행하기

원문 보기 위키 갱신

Fly Machines API가 사용자 코드 실행이나 일시적인(ephemeral) 작업에 어떻게 쓰이는지, 그리고 flyctl로 개별 Machine을 관리하는 방법을 배우는 가이드예요. 이 문서는 Fly Apps V2 이전에 작성되어 예시를 fly.m run 계열 명령으로 구성하지만, 아직도 개별 Machine 단위로 작업할 때 유용하게 쓰입니다.

출처: 문서

본문

이 문서는 오래된 내용입니다!

이 문서는 flyctl로 개별 Fly Machines를 다루는 몇 가지 방법을 보여주며, Fly Apps V2가 fly.toml 설정 파일과 fly deploy로 Machines를 그룹으로 관리할 수 있게 되기 전에 작성되었습니다. 대부분의 웹 앱에서는 Fly Apps 문서부터 시작하는 것이 더 좋아요!

우리는 사용자 코드를 실행하거나 일시적인 작업을 위해 Machines API를 적극 추천해요. Fly Machines는 Fly.io 플랫폼-as-a-service V2의 VM 구성 블록이기도 합니다.

flyctl은 앱을 fly deploy하는 것보다 더 세밀한 수준에서, CLI로 개별 Machine을 관리하는 명령을 제공합니다.

fly machine 명령 문서도 좋은 참고 자료예요.

Machines 웹 앱의 구성 요소

무상태(stateless) 앱을 실행하는 단일 Machine을 만들어 세계에 <app-name>.fly.dev로 서비스하려면 다음 조각을 조립해야 해요.

  1. Machines 방식의 Fly App — fly deploy로 앱의 Machines를 관리하는 것은 아니지만, VM과 anycast IP, 볼륨 같은 것들을 소유하는 관리 엔터티로서 앱은 여전히 필요해요.
  2. 최소한 IPv4 공개 anycast IP 주소 하나
  3. 노출하려는 HTTP 서비스의 포트 매핑
  4. Docker 이미지

우리는 Docker 이미지에서 VM을 만들어 내므로, 플랫폼에 이미지를 얻거나 빌드하는 방법을 알려줘야 해요.

이 중 어느 것도 Machines에만 특별한 것은 아니에요. 여러분 프로젝트를 위한 화려한 런처를 아직 만들지 않았다면, fly launch로 시작하는 V1(Nomad) 앱에서도 직접 해야 하는 일입니다.

여기서 큰 차이는 앱 전체 설정을 fly.toml 파일에 넣고 fly deploy를 실행하는 대신, VM을 생성하는 fly m run(일명 fly machine run) 명령에 VM별 설정을 통합한다는 점이에요.

현재 작업 디렉터리에 Dockerfile이 있고 그 Dockerfile이 사용하는 소스 코드도 있다고 상상해 보세요. 빌드하면 내부 포트 8080에서 듣는 무언가를 위한 이미지가 나옵니다. 브라우저가 HTTPS 연결을 보내는 포트 443에서 세계에서 접근 가능하게 만들고 싶어요.

질서 정연하게 하고 싶다면 Machine을 부팅하기 전에 App에 공개 IP 주소를 갖게 하고 싶을 거예요.

fly apps create --machines --name testrun
fly ips allocate-v4 --shared -a testrun
fly ips allocate-v6 -a testrun
fly m run . -p 443:8080/tcp:tls:http -p 80:8080/tcp:http --region yyz -a testrun

마지막 명령은 VM을 구성하고 배포합니다. 여기 있는 .이 보이나요? 이 경우 그것은 Dockerfile의 경로입니다. VM이 실행되기 위해 제공해야 하는 모든 것을 나타내는, 너무 작아서 놓치기 쉬운 표기예요. 빼먹지 마세요.

위에서 공유 IPv4 주소 할당을 주목하세요. 일반 HTTP(S) 웹 앱에는 공유 IPv4면 충분하고, 지구상의 줄어드는 IPv4 공급을 보존하는 데 도움이 되며(비용 절약도 됩니다). 앱이 다른 서비스를 운영하고 전용 IPv4가 필요하다면 --shared 플래그를 빼면 전용 IP를 프로비저닝할 수 있어요.

앱이 아직 없다면 fly m run이 만들 것인지 물어보므로, 실제로는 이렇게 해도 됩니다.

fly m run . -p 443:8080/tcp:tls:http -p 80:8080/tcp:http --region yyz -a testrun
fly ips allocate-v4 --shared -a testrun
fly ips allocate-v6 -a testrun

예시: 간단한 공개 웹 앱

이것을 예시로 더 구체화해 봅시다. 거의 정적인 내용을 가진 작은 Python/Flask 앱을 실행하고 싶어요.

간단한 웹 앱에서 개별 Machine을 제어하는 수준까지 갈 필요는 없어요. fly launch를 실행해 Fly App을 구성하고 fly deploy의 편의를 누리면 되지만, 원한다면 이렇게 할 수도 있어요.

주의! Machines 앱의 구조를 익히기 위해 작은 실수도 한두 개 만들 거예요.

리포지토리에서 코드와 Dockerfile을 가져올게요.

git clone https://github.com/fly-apps/hello-gunicorn-flask

그런 다음 디렉터리로 들어가고.

cd hello-gunicorn-flask

그냥 시도해 봅니다.

fly apps create --machines --name testrun 
fly ips allocate-v4 --shared -a testrun
fly ips allocate-v6 -a testrun
fly m run . -p 443:8080/tcp:tls:http -p 80:8080/tcp:http --region yyz -a testrun

이런 메시지를 받으면:

Success! A machine has been successfully launched in app testrun, waiting for it to be started
 Machine ID: 21781160b41d89
 Instance ID: 01GMF1WWXBEQR0RSHAN7XVWPK4
 State: created
Machine started, you can connect via the following private ip
  fdaa:0:3b99:a7b:aa4:3c65:c2cc:2

내 앱을 확인할 수 있어요.

fly apps open -a testrun

…그런데 아무것도 안 나와요. (저는 대부분의 flyctl 명령에 -a를 붙이는 점에 유의하세요. 설정 파일로 fly.toml을 사용하지 않기 때문이에요. 보통 flyctl은 작업 디렉터리의 fly.toml에서 앱 이름을 가져오는데, 지정하지 않으면 그렇다고 가정하기 때문이죠.)

무엇이 잘못됐을까요?

일단 기본인 fly status -a testrun부터 시작하세요.

fly status -a testrun
App
  Name     = testrun          
  Owner    = personal         
  Hostname = testrun.fly.dev  
  Platform = machines         

ID              STATE   REGION  HEALTH CHECKS   IMAGE                                           CREATED                 UPDATED              
21781160b41d89  started yyz                     testrun:deployment-01GMF1WS8C28CDDYAFBVHA8F0Q   2022-12-17T03:26:30Z    2022-12-17T03:26:32Z

VM이 started 상태예요. fly m status로 더 자세한 내용을 볼 수 있어요.

fly m status 21781160b41d89 -a testrun
Machine ID: 21781160b41d89
Instance ID: 01GMF1WWXBEQR0RSHAN7XVWPK4
State: started

VM
  ID            = 21781160b41d89                                 
  Instance ID   = 01GMF1WWXBEQR0RSHAN7XVWPK4                     
  State         = started                                        
  Image         = testrun:deployment-01GMF1WS8C28CDDYAFBVHA8F0Q  
  Name          = lingering-shape-1541                           
  Private IP    = fdaa:0:3b99:a7b:aa4:3c65:c2cc:2                
  Region        = yyz                                            
  Process Group =                                                
  Memory        = 256                                            
  CPUs          = 1                                              
  Created       = 2022-12-17T03:26:30Z                           
  Updated       = 2022-12-17T03:26:32Z                           
  Command       =                                                

Event Logs
STATE   EVENT   SOURCE  TIMESTAMP                       INFO 
started start   flyd    2022-12-16T22:26:32.888-05:00
created launch  user    2022-12-16T22:26:30.338-05:00

여기서는 모든 게 정상으로 보여요. 로그는 어떨까요?

fly logs -a testrun

프록시에서 [info]Still waiting for machine to listen on 0.0.0.0:8080 (waited 43.329214343s so far) 같은 메시지와 error.message="failed to connect to fly machine: Gave up after 50 retries" 메시지를 많이 볼 수 있어요. 즉 Machine은 시작됐지만, 제가 Fly Proxy에 알려준 곳에서 듣고 있지 않다는 뜻이에요. 로그 시작 부분에 더 가까이 스크롤하면:

[INFO] Listening at: http://0.0.0.0:4999 (512)

앗, 제가 Fly Proxy에 내부 포트 8080으로 요청을 보내라고 했는데, 실제로 프로세스는 4999 포트에서 듣고 있었어요!

Machine 설정 업데이트

Machines 대신 Fly App으로 작업했다면 fly.toml의 내부 포트를 수정하고 다시 배포했을 거예요. Fly Machines의 방식으로 동일하게 해 봅시다. fly m update에 설정 변경을 지정합니다.

fly m update -p 443:4999/tcp:tls:http -p 80:4999/tcp:http 21781160b41d89 -a testrun
Searching for image 'registry.fly.io/testrun:deployment-01GMF1WS8C28CDDYAFBVHA8F0Q' remotely...
image found: img_3mno4w881q64k18q
Image: registry.fly.io/testrun:deployment-01GMF1WS8C28CDDYAFBVHA8F0Q
Image size: 54 MB

Configuration changes to be applied to machine: 21781160b41d89 (lingering-shape-1541)

  	{
      ...
  	... // 2 identical lines
  			{
  				"protocol": "tcp",
-  				"internal_port": 8080,
+  				"internal_port": 4999,
  				"ports": [
  					{
  	... // 8 identical lines
  			{
  				"protocol": "tcp",
-  				"internal_port": 8080,
+  				"internal_port": 4999,
  				"ports": [
  					{
  	... // 15 identical lines
    
? Apply changes? Yes
Updating machine 21781160b41d89
No health checks found
Machine 21781160b41d89 updated successfully!
==> Monitoring health checks
No health checks found

Monitor machine status here:
https://fly.io/apps/testrun/machines/21781160b41d89

VM이 업데이트된 설정으로 재시작됩니다.

이제 fly apps open -a testrun을 다시 실행하면, 짠!

(따라 하면서 이 예시 앱을 배포했다면 https://<your-app>.fly.dev/<your-name>로 접속해 보세요.)

Machine의 Docker 이미지 업데이트

가상의 미래의 나를 상상해 보세요. 여러 리전에 앱을 실행하는 여러 VM이 있어서 전 세계 사용자에게 더 빠르게 서비스하고 있죠. 그런데 내부 WireGuard 네트워크의 프라이빗 IPv6로 앱의 VM 중 하나에 GET 요청을 보내다 보니, 프로세스가 IPv6로 듣도록 구성하지 않았다는 걸 발견했습니다! 고치려면 Docker 이미지 안의 무언가를 바꿔야 하고, Machines에서 실수를 고치는 주제를 다루는 김에 지금 살펴볼게요.

V1 App에서는 이걸 fly deploy로 했겠죠. fly m update로 Machine을 재빌드하고 재시작할 수 있을까요? 물론이죠!

fly m update 21781160b41d89 --dockerfile ./Dockerfile -a testrun
...
--> Pushing image done
Image: registry.fly.io/testrun:deployment-01GMPHMHN24ZCHR4Q0RW8QE99R
Image size: 141 MB

Configuration changes to be applied to machine: 21781160b41d89 (lingering-shape-1541)

  	... // 6 identical lines
  			"tty": false
  		},
-  		"image": "registry.fly.io/testrun:deployment-01GMPH92RA8WRFFTZH0RM1KQMJ",
+  		"image": "registry.fly.io/testrun:deployment-01GMPHMHN24ZCHR4Q0RW8QE99R",
  		"metadata": null,
  		"restart": {
  	... // 26 identical lines

? Apply changes? Yes
Updating machine 21781160b41d89
No health checks found
Machine 21781160b41d89 updated successfully!

Monitor machine status here:
https://fly.io/apps/testrun/machines/21781160b41d89

이걸 3개 인스턴스에 수동으로 하는 건 끔찍하진 않지만, 20개라면 꽤 지루하겠죠. 앱의 모든 VM이 같은 이미지에서 실행되어야 한다면 더 나은 방법이 있어요. fly image update입니다.

새 이미지를 빌드해 Fly.io 이미지 저장소에 푸시하려면:

fly deploy --image-label v2 --build-only --push -a testrun

라벨을 생략해도 되지만, 합리적인 라벨은

fly image update --image registry.fly.io/testrun:deployment-01GMHP0PH4NQD7EBTGXPNEFDJR -a testrun

와

fly image update --image registry.fly.io/testrun:v2 -a testrun

을 구분하는 차이가 돼요. 후자로 앱의 모든 VM을 새 이미지로 업데이트할 수 있습니다.

다른 리전에 인스턴스 추가

새 Machine을 만들어 추가

다른 VM들이 실행 중인 것과 같은 이미지로 fly m run을 사용해 새 Machine을 만들 수 있어요. 같은 App에서 다른 리전에요 (이번에는 올바른 포트를 사용하세요).

fly m run registry.fly.io/testrun:v12 -p 443:4999/tcp:tls:http -p 80:4999/tcp:http --region syd -a testrun

Machine을 클론해 추가

아니면 fly m clone으로 기존 인스턴스를 복제해 같은 효과를 낼 수 있어요 (이 명령은 변경 중이며, 나중에 일반 VM 사용에서는 더 이해하기 쉬울 거예요).

fly m clone 21781160b41d89 --region ord -a testrun
Cloning machine 21781160b41d89 into region ord
Provisioning a new machine with image registry.fly.io/testrun:v12...
  Machine 9080567add5087 has been created...
  Waiting for machine 9080567add5087 to start...
No health checks found
Machine has been successfully cloned!

이 시점에서 앱을 배포하고, 배포 중 만든 실수를 고치고, 전 세계 여러 데이터센터로 확장했습니다. 이 앱을 관리하면서 유용할 몇 가지 다른 것들이 있습니다.

Machine에서 셸 열기

Hallpass라는 프로그램 덕분에 flyctl과 유저 모드 WireGuard만으로 이 작업을 할 수 있어요. 이 프로그램은 각 Fly.io App VM에서 실행됩니다.

fly ssh console -s로 특정 Machine에 ssh로 접속할 수 있어요.

fly ssh console -s -a testrun
? Select VM:  [Use arrows to move, type to filter]
> ord: 9080567add5087 fdaa:0:3b99:a7b:8ba9:46d4:87d6:2 polished-mountain-3926
  yyz: 21781160b41d89 fdaa:0:3b99:a7b:aa4:3c65:c2cc:2 lingering-shape-1541
  lax: 9185340f472683 fdaa:0:3b99:a7b:7d16:958:b788:2 white-shape-8276

ssh로 들어갈 수 있다면 확실히 살아있다는 증거예요. 그 안에서 수정도 할 수 있어요. 다만 루트 파일시스템에 한 모든 변경(마운트된 Fly Volume이나 별도 데이터베이스 앱이 아닌 것)은 Machine을 다음에 시작할 때 되돌아간다는 점을 명심하세요.

참고: 프로젝트에 어떤 종류의 셸이 설치되어 있어야만 VM에서 콘솔 인터페이스를 얻을 수 있어요.

VM 중지로 임시 축소

fly m stop으로 Machine을 중지하고, fly m start로 다시 시작합니다.

여기 미묘한 점이 있어요. 공개 서비스가 정의되어 있으면, Fly Proxy가 우연히 중지된 Machine에 요청을 보낼 때 그 Machine도 시작시키려 한다는 점입니다. 이를 완화하는 한 방법은 Machine이 일정 시간 유휴 후 메인 프로세스가 종료되어 VM을 종료하도록 하는 거예요. 그러면 프록시가 무작위 봇 요청 때문에 VM을 깨워도 무기한으로 유지되지는 않습니다.

영구 축소

fly m stop을 실행한 뒤 fly m remove로 Machines VM을 완전히 제거하세요.

앱 삭제

전체 App과 그 모든 Machines, IP 주소, Volumes 등을 없애고 싶다면 V1 App과 마찬가지로 fly apps destroy를 실행하면 돼요.

더 알아보기 (Learn more)