title image

s6 overlay 소개

Docker는 하나의 컨테이너에 하나의 프로그램을 띄우는 것이 원칙입니다. 그러나, 지금까지 이런 식으로 프로그램을 작성해 본 경험이 적어서인지 아니면 그냥 현실성이 떨어지는 원칙이어서 인지는 확실치 않습니다만, 이런 방법으로 프로그램을 만든다는게 별로 쉬운일은 아닙니다.

그래서 많은 사람들이 Docker image를 만들 때, Supervisor 같은 프로그램을 사용하고 있습니다.

s6 overlay?

s6 overlays6를 이용해서만든 Docker용 supervisor입니다. 그래서 Supervisor에 비해 Docker에서 사용하기 좋습니다. 이번 글에서는 s6 overlay의 특징에 대해 간단히 정리를 해보려합니다.

특징

라이프 사이클

s6 overlay의 라이프 사이클은 Stage 1, Stage 2 그리고 Stage 3로 구분됩니다. 이 중, Stage 1은 s6 overlay가 실행되는 단계입니다. 그래서 당장은 크게 신경쓸 단계는 아닙니다. 중요한건 Stage 2 그리고 Stage 3인데요, Stage 2는 Stage 1 이후에 호출되며 이 단계에서는 컨테이너를 초기화한 뒤, 서비스를 실행합니다. Stage 3는 서비스를 종료하는 단계로, 이 단계가 끝나면 컨테이너도 종료됩니다.

컨테이너 초기화, 서비스 실행 그리고 컨테이너 종료에 대해서는 아래에서 조금 더 자세히 설명하도록 하겠습니다.

모든 내용은 폴더를 이용해서 관리합니다.

supervisor는 .conf 파일을 이용해서 여러 프로그램을 관리합니다. 반면, s6 overlay는 폴더(디렉토리)를 이용해서 관리합니다. 컨테이너 초기화는 /etc/cont-init.d에서, 서비스는 /etc/services.d에서, 컨테이너 종료는 /etc/cont-finish.d에서 관리합니다.

서비스의 중요도에 따라 컨테이너의 수명을 관리할 수 있습니다.

비정상적으로 종료가 되더라도 그저 다시 실행시키기만 해도 되는 프로그램이 있습니다. cron 같은게 대표적일 겁니다. 반면 어떤 프로그램은 종료될 경우, 컨테이너를 다시 실행시켜야 할 수도 있습니다.

s6 overlay는 프로그램의 성격에 따라 서비스만 재시작을 할지, 컨테이너를 종료시킬지 결정할 수 있습니다. 정확히는 프로그램이 종료된 경우 후처리를 진행할 수 있는데, 이 때 컨테이너를 종료시킬 수 있습니다. 후처리 단계에서 컨테이너를 종료시키지 않는 경우, 서비스는 자동으로 재시작 됩니다.

각 단계별 설명

컨테이너 초기화

문자 그대로, 컨테이너 초기화 단계입니다. 여기서는 서비스 실행 전에 수행해야할 동작을 미리 지정할 수 있습니다. 파일의 권한의 수정이나, 토큰을 재발급 받는 등의 일을 여기서 수행할 수 있습니다.

/etc/cont-init.d에 스크립트 파일을 복사해 넣으면, 각 파일을 순서대로 수행합니다. 정렬 순서는 파일명입니다.

일반적으로, 초기화 스크립트가 실패해도 서비스는 수행됩니다. 이를 막으려면 환경변수 S6_BEHAVIOUR_IF_STAGE2_FAILS값을 2로 설정하면 스크립트 실패 시 컨테이너 수행도 중단됩니다.

S6_BEHAVIOUR_IF_STAGE2_FAILS의 값이 0인 경우 실패 시 조용히 진행, 1인 경우 에러 메시지 출력 후 진행합니다.

서비스 실행

컨테이너의 초기화가 끝나면, 서비스를 실행합니다. 서비스는 몇 개든 상관 없습니다. 주의할 점이 있다면 Foreground로 실행해야 한다는 점 뿐입니다. (하지만 이건 Supervisor도 마찬가지죠.)

서비스를 실행하기 위해서는 /etc/services.d에 서비스 별로 하위 폴더를 만들어야 합니다. 그리고 각 폴더에는 run파일이 반드시 필요합니다. run파일에는 서비스를 실행하는 스크립트를 작성하면 됩니다.

그리고, 필요한 경우 finish 파일을 추가할 수 있습니다. 이 파일은 서비스가 종료되었을 때 수행됩니다. finish에서는 프로그램이 종료되었을 때 해야할 일을 수행하면 됩니다. 로그를 정리할 수도 있고 Slack에 메시지를 보내는 것 등이 가능합니다.

finish 수행이 완료되면 run이 수행됩니다. 즉 서비스는 계속 재시작 됩니다. 다만, 재시작이 불가능하거나 바람직하지 않은 경우에는 s6에 종료 시그널을 전달하는 방법으로 컨테이너 수행을 종료할 수 있습니다.

조금 의아한건, S6_BEHAVIOUR_IF_STAGE2_FAILS를 2로 설정하더라도, 서비스 스크립트는 실패 여부와 관계없이 계속해서 수행을 한다는 겁니다. 이 부분은 조금 더 알아봐야할 것 같습니다.

컨테이너 종료

컨테이너를 종료하는 단계입니다. 비 정상적인 종료인 경우 메일이나 슬랙으로 메시지를 남기는 등, 뭔가 필요한 작업이 있다면 이 단계에서 수행하면 됩니다. 물론 컨테이너를 종료하기 전에 서비스가 먼저 종료됩니다.

/etc/cont-finish.d에 있는 스크립트 파일들이 순서대로 수행됩니다.

서비스 로그

서비스 폴더 밑에 log폴더를 만들고, 그 안에 run 스크립트를 작성하면 됩니다. s6 overlay의 로그는 조금 특이합니다. 서비스가 출력한 모든 내용(stdout)을 log/run에 stdin으로 전달합니다. 익숙치 않은 방식이라 이 방식의 장점은 잘 모르겠습니다.

예제

/
|--etc
|  |--cont-init.d
|  |  |-- init1
|  |  |-- init2
|  |
|  |--services.d
|  |  |--service1
|  |  |  |--run
|  |  |--service2
|  |  |  |--run
|  |  |  |--finish
|  |
|  |--cont-finish.d
|  |  |-- finish1
|  |  |-- finish2

설명만으로는 조금 복잡해서, 예제로 폴더 구조를 간단하게 적어보았습니다. 위의 도표를 이용해서 실행 과정을 간단하게 설명하면

컨테이너 초기화

/etc/cont-init.d아래에 있는 init1, init2가 순서대로 수행됩니다.

서비스 실행

초기화가 끝나면, service1과 service2를 실행해야 합니다. 각 서비스를 실행하는 스크립트는 순서대로 /etc/services.d/service1/run/etc/services.d/service2/run입니다.

컨테이너 종료

컨테이너를 종료하는 경우, 서비스가 먼저 종료되어야하니 /etc/services.d/service2/finish가 먼저 수행되고, 이후 /etc/cont-finish.d아래에 있는 finish1, finish2가 순서대로 수행됩니다.

환경 변수 전달은 with-contenv로

보통 실행 가능한 스크립트를 만들 때, 환경 변수를 전달하기 위해 #!/usr/bin/env를 붙입니다. s6 overlay에서는 #!/usr/bin/env 대신 #!/usr/bin/with-contenv를 사용합니다. 이 경우 컨테이너에 설정된 모든 환경변수가 전달되는것이 보장됩니다. 환경 변수로 인한 디버깅 과정을 크게 줄일 수 있습니다.

모든 스크립트의 CWD는 그 스크립트가 있는 폴더 입니다.

이건 어찌보면 당연한건데, 또 어찌보면 특이하기도 합니다. 많은 프로그램은 자신의 CWD가 부모 프로그램의 CWD인 경우가 많습니다. s6 overlay는 해당 스크립트가 곧 그 스크립트의 CWD가 됩니다.

예를 들어, /etc/services.d/very-important/run이라는 스크립트 파일이 있다고 하겠습니다. very-important라는 서비스가 run을 통해서 수행이 될텐데요, 이 과정을 거치면서 일반적으로 very-important라는 서비스의 CWD는 /etc/services.d/very-important가 되어버립니다.


Posts