Move from standalone container to separate web and data containers

Jay @pfaffman 님, 안녕하세요. 이 ‘두 컨테이너’ 주제에 대해 이 글과 다른 글들, 그리고 Sam 님의 글까지 감사드립니다.

질문드립니다:

말씀하신 대로 data용 컨테이너 하나와 web-only용 컨테이너 하나를 설정하는 것을 시도해 왔는데, macOS에서 이를 구동하는 과정에서 여러 가지 문제가 발생하고 있습니다.

하지만 Mac이나 Ubuntu에서 이 '두 컨테이너 구성’을 디버깅하는 것에 대해 걱정하기 전에, 우리가 이 일을 하는 것이 올바른 이유인지 먼저 확인하고 싶습니다.

'두 컨테이너 방식’을 시도하려는 이유는 웹 앱을 재구축할 때(예: 플러그인 설치 시) 사이트가 다운되지 않도록 하기 위해서입니다. 또한, 자체 개발한 플러그인을 수정할 때, 변경 사항이 작동하도록 하는 유일한 방법이 재구축인 경우가 있다는 것을 알게 되었습니다(이것은 다른 날 이야기할 내용입니다). 또한, 제 마음에 드는 ‘빠르고 친근한’ 웹 개발 환경을 구축하는 것도 고군분투하고 있지만, 그것도 또 다른 주제입니다.

따라서 제 질문은, ‘두 컨테이너’ 구성이 웹 앱의 웹 전용 부분이 재구축될 때 다운타임을 현저히 줄여주는지 여부입니다.

이것이 이 문제를 생각하는 올바른 방식이 맞죠, 그렇죠?

플러그인을 설치하거나 수정할 때, ‘web-only’ yml 파일만 재구축하면 되고 data yml 파일은 재구축하지 않아도 되는 것인가요?

우리는 LAMP 포럼 배경에서 왔기 때문에, 플러그인 변경은 대부분 라이브 사이트에서 런타임으로 이루어집니다(실수를 하지 않는 한 다운타임이 없습니다). 또한, VueJS 웹 앱에서 데스크톱에서 빌드한 후 새 앱을 업로드하고 제자리에 배치하면 거의 다운타임 없이 업그레이드/업데이트가 가능했습니다. 그러나 Discourse에서는 다운타임이 발생하는데, 우리는 이를 원하지 않습니다(몇 초라도).

‘두 컨테이너’ 솔루션이 (1) 앱을 재구축할 때(플러그인, 코드 수정 등) 또는 (2) 전체 백업에서 복원할 때 다운타임에 있어 상당한 개선 효과를 보이는가요?

우리는 Discourse를 프로덕션 환경에서 실행하고 거의 제로 다운타임으로 변경 사항을 적용하는 방법을 찾고 있지만, LAMP이나 VueJS 앱(예를 들어)에서처럼 쉽게 할 수 있는 방법을 아직 찾지 못했기 때문에 이 질문을 하면 또 '마구잡이로 비판’을 당할 것 같은 기분이 듭니다.

그래서 아직 구동하지 못한 ‘두 컨테이너’ 방식에 대한 고군분투와 관심이 있는 것입니다.

감사합니다!

네. 새 컨테이너가 빌드되는 동안 기존 웹 컨테이너는 계속 실행됩니다. 따라서 다운타임은 새 웹 서버를 시작하는 데 걸리는 시간이며, 이는 보통 1분 미만이지만, 이를 제로 다운타임(Zero-downtime)이라고 부르지는 않습니다. 제로 다운타임을 원한다면, 구 컨테이너를 종료하기 전에 새 컨테이너가 완전히 시작되고 작업을 처리할 수 있도록 해주는 리버스 프록시를 앞에 배치해야 합니다. (그리고 새 컨테이너를 위한 데이터베이스 마이그레이션이 구 컨테이너에 문제를 일으키는 경우, 다른 복잡한 절차를 거치지 않는 한 거기서도 다운타임이 발생합니다.)

백업 복원 시에는 차이가 없습니다.

4개의 좋아요

Jay @pfaffman 님, 감사합니다.

말씀하신 대로, 정말로 여기서는 최고급의 소중한 자원입니다!

제(아직은 제한적인) 이해를 바탕으로 한 이 다소 미친(?) 아이디어에 대해 어떻게 생각하시나요:

다음 튜토리얼에 따라 프론트엔드에서 nginx를 리버스 프록시로 설정합니다:

그런 다음 discourse_docker(스탠드얼론)이 설정된 두 개의 디렉터리/인스턴스를 만듭니다. 예를 들어:

  1. /var/discourse1
  2. /var/discourse2

이 두 인스턴스 모두에서 discourse_docker(스탠드얼론)을 서로 다른 소켓을 경청하도록 설정하며, 각 인스턴스에서 다음 템플릿을 수정합니다:

 - "templates/web.socketed.template.yml"

즉, 요약하자면, 우리는 조용한 시간에 프로덕션 환경을 다시 빌드하여 다른 컨테이너에서 서로 다른 소켓(nginx.https.sock2)을 경청하도록 실행하므로 소켓 충돌이 발생하지 않습니다. 이를 통해 스탠드얼론 모드에서도 빌드할 수 있으며(두 컨테이너, 즉 data와 web-only의 필요성을 제거하는 것이 목표입니다).

예를 들어(논의/설명용), discourse1의 web.socketed.template.yml에서:

  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 80;/
     to: |
       listen unix:/shared/nginx.http.sock;
       set_real_ip_from unix:;
  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 443 ssl http2;/
     to: |
       listen unix:/shared/nginx.https.sock ssl http2;
       set_real_ip_from unix:;

discourse2에서는:

 - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 80;/
     to: |
       listen unix:/shared/nginx.http.sock2;
       set_real_ip_from unix:;
  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /listen 443 ssl http2;/
     to: |
       listen unix:/shared/nginx.https.sock2 ssl http2;
       set_real_ip_from unix:;

그러나 discourse 템플릿이 마법을 부리게 하는 대신, 우리는 단순히 /etc/nginx/conf.d/discourse.conf에서 소켓을 수동으로 전환하고 nginx를 재시작합니다. 따라서 web.socketed.template.yml 템플릿에서 replace: 지시문을 제거하게 됩니다.

이 제안된(아마도 미친) 구성에서는, 두 개의 스탠드얼론 컨테이너가 서로 다른 소켓(충돌 없음)을 경청하도록 하고, nginx를 원하는 소켓에 연결하도록 구성한 후 nginx를 재시작하면 됩니다.

이는 명확하고 쉬우며, 라이브 인스턴스에서 새 게시글이 없는 느린 기간 동안 유용할 수 있습니다. 단일 discourse 인스턴스(앱)당 두 개의 컨테이너(data와 web-only)의 복잡성을 원하지 않거나 필요로 하지 않는 사람들에게는 특히 그렇습니다.

물론, 데이터 관점에서 가장 견고한 구성, 특히 완벽함을 추구하는 바쁜 사이트의 경우, “두 컨테이너” 솔루션이 될 것입니다. 왜냐하면 우리는 data와 web-only 인스턴스(이제 두 개의 서로 다른 소켓, sock와 sock2를 경청)를 원할 것이기 때문입니다.

nginx 프론트엔드를 갖춘 "두 컨테이너 솔루션"에서 "표준 구성"은 두 개의 web-only 컨테이너가 동일한 소켓을 경청하도록 하는 것이므로, 두 컨테이너는 동시에 실행할 수 없습니다. 하지만(예를 들어) 서로 다른 소켓을 경청하도록 한다면, 두 컨테이너는 동시에 실행할 수 있고, 우리는 단순히 nginx 설정 파일(과 nginx 재시작)을 사용하여 두 컨테이너 사이를 전환할 수 있습니다.

이것이 올바른 이해인가요?

(천천히 하지만 확실히) 이해하기 시작하고 있는 건가요?

감사합니다!

후속 노트: 제 데스크톱 Mac 중 하나에서 “두 컨테이너” 구성이 작동하는 것을 확인했습니다:

Screen Shot 2020-04-11 at 12.41.24 PM

설치 시 유일한 주의사항은, 스크립트가 어떤 이유에서인지 생성하지 않는 다음 디렉터리를 수동으로 생성하고(소유권과 권한을 설정) 해야 한다는 것입니다:

~discourse/discourse/shared/data
~discourse/discourse/shared/web-only

그리고 물론, 처음에는 데이터베이스에 빈 비밀번호를 사용해서 시도했는데, 그것은 작동하지 않았습니다(설명서에는 비밀번호를 설정하라고 되어 있지만, 저는 단순히 실험을 하고 있었기 때문입니다).

다음으로, nginx 프론트엔드를 설정하고 web-only 앱에 대해 websocket을 사용하여 해당 구성으로의 이동을 시도할 것입니다.

내용이 꽤 많네요. 하지만 discourse 디렉터리를 두 개 만들 이유는 없습니다. containers 디렉터리에 yml 파일을 여러 개 만들면 됩니다. 파일 이름은 원하는 대로 지어주세요.

2개의 좋아요

확인해 주셔서 감사합니다. 오늘 실험을 거쳐 현재 설정(단일 디렉터리)이 정확히 그 방식입니다.

2컨테이너 구성(2CC)에서는 모든 것이 잘 작동했지만, macOS에서 nginx 리버스 프록시 설정에 어려움을 겪고 있습니다.

소켓은 컨테이너 외부에서 접근 가능함에도 /shared 디렉터리의 유닉스 도메인 소켓으로 연결을 확립하지 못하고 있습니다. nginx는 물론 python과 socat(테스트용)으로도 시도해 보았지만, 항상 61 연결 거부 오류가 발생합니다. 헷갈리네요.

“연결 거부” 오류 때문에 하루 종일 막혀 있었습니다!!

내일은 또 다른 날이겠죠.

작은 질문이 하나 있습니다.
컨테이너 설정이 하나뿐인 경우(단순히 'app.yml’만 있는 경우)에 ./launcher bootstrap app을 실행하면, 우리 웹사이트/프론트엔드가 중단되나요, 아니면 계속 실행되나요?

중단된다면, 왜 'bootstrap web-only’는 웹사이트를 중단하지 않는 건가요?

중단되지 않는다면, 컨테이너 재빌드 시 시간 단축 측면에서 두 컨테이너 구성의 장점은 무엇인가요?换句话说, 단일 컨테이너를 부트스트랩하는 동안에도 웹사이트를 실행 상태로 유지할 수 있다면, 왜 두 개의 별도 컨테이너가 필요한가요?

1개의 좋아요

새로운 .yml 파일을 만들어서 예를 들어 new_image라고 이름 붙인다면 중단되지 않습니다.

정확하게 설정된 경우, 부트스트랩(bootstrap) 과정은 어떤 서비스도 시작하거나 중단하지 않습니다. 부트스트랩된 이미지는 실행 상태가 아닙니다. 그래서 "bootstrapped(부트스트랩된)"이라고 불리는 것입니다.

그러나 새로운 이미지 이름으로 새로운 이미지를 생성해야 하므로 새로운 yml 파일을 만들어야 합니다.

즉, 새로운 이미지 이름을 가진 경우, 새로 부트스트랩된 이미지는 아직 실행되지 않았습니다. 단지 “bootstrapped(부트스트랩된)” 상태일 뿐입니다.

새로운 이름(app가 아닌 이름)으로 새 이미지를 빌드할 수 있으며, 빌드 부분은 여기서 완료됩니다. 이를 "new_image"라고 부르겠습니다.

그런 다음, 실행 중인 이미지(이를 "old_image"라고 부르겠습니다)를 교체하고 싶다면 다음과 같이 할 수 있습니다:

./launcher stop old_image; ./launcher start new_image

질문자의 경우:

./launcher bootstrap new_image          #데이터 이미지와 웹 "app" 이미지가 실행 중
./launcher stop app; ./launcher start new_image

데이터 컨테이너가 이미 빌드되어 있으므로, (1) 데이터 이미지를 빌드하지 않고 (2) 다른 이미지들이 실행되는 동안 웹 이미지를 빌드(이미지 부트스트랩)할 수 있기 때문에 웹 이미지를 다시 빌드할 때 다운타임이 없습니다. 이를 통해 시간을 절약할 수 있습니다.

이 방식은 훨씬 빠릅니다. 하지만 컨테이너 앞에 리버스 프록시를 사용하는 것만큼은 빠르지 않습니다.

질문자의 원래 메시지에서는 app이라는 이름의 이미지가 실행 중입니다. 만약 다시 app이라는 이름의 이미지를 부트스트랩하려고 하면, 동일한 이미지(이름)를 다시 빌드하는 것이 됩니다. 직감처럼 말씀하신 대로, 이는 시간을 절약해 주지 않습니다.

이제 명확해졌나요?

아니면, 질문해 주세요. 누구나 배울 수 있고, 배우는 것은 흥미로운 일입니다. 이 개념이 처음이라면 조금 시간이 걸리지만, 실제로는 이해하기 쉽습니다.

2개의 좋아요

솔직히 말하면, 아무것도 이해가 되지 않았습니다. 시도해 보았지만 실패했습니다.

제 질문은 단순했습니다.
2개의 컨테이너 구성을 사용하는 경우, ‘web-only’ 컨테이너를 ‘bootstrap’(재빌드하지 않고)하면, 현재 웹 컨테이너가 계속 작동합니다(웹사이트가 정상적으로 표시되므로).

그렇다면 'app.yml’과 같은 단일 컨테이너 구성을 사용하는 경우, 이 ‘app’ 컨테이너를 bootstrap하면 웹사이트가 계속 작동할까요?

그리고, 설명이 단순하다면 왜 그런 건가요?

1개의 좋아요

누군가를 고용해서 도움을 받으시는 게 어떨까요?

1개의 좋아요

문제가 아닙니다.

작은 궁금증이었을 뿐이에요. 어쩌면 '예/아니오’로 답변할 수도 있는 부분이죠.

큰일은 아닙니다.

1개의 좋아요

제 제안은 직접 시도해 보며 즐기는 것입니다.

실제로 포럼에서 질문을 하고 답변을 기다리는 것보다, 나만의 테스트베드에서 직접 시도해 보는 것이 훨씬 적은 시간이 걸립니다.

직접 시도해 보면 질문에 대한 답을 얻을 수 있으니, 프로덕션 앱을 깨뜨리지 않도록 테스트 시나리오에서 이러한 것들을 시도해 보세요. :slight_smile:

Discourse는 공동 창립자들의 너그러움 덕분에 오픈 소스이며 다운로드와 설정이 무료입니다. 즉, 여러분은 이 오픈 소스를 활용하여 테스트 앱을 자유롭게 생성하고, 파괴하고, 다시 생성하고, 다시 파괴할 수 있으며(원하는 만큼) 그래야 합니다.

기본적인 시스템 관리 작업에 조금이라도 노력을 기울일 의향이 없다면, @codinghorror 님이 여기서 지역 인재를 고용하라는 훌륭한 제안을 하셨습니다.

1개의 좋아요

네. (부트스트랩이 데이터베이스를 마이그레이션하여 실행 중인 컨테이너가 더 이상 이를 사용할 수 없게 만드는 경우가 아니라면요). 구 컨테이너가 종료되고 새 컨테이너가 부팅되는 동안에는 다운타임이 발생합니다.

아니요. 두 개의 데이터베이스 프로세스가 동시에 동일한 파일에 액세스할 수 없습니다.

3개의 좋아요

아!!
이 부분을 명확히 해줘서 감사합니다.

1개의 좋아요

따라서 데이터베이스를 빌드 중인 컨테이너에 포함하지 않으면, 다른 웹 컨테이너가 계속 작동하는 동안 데이터베이스를 조작하는 새로운 웹 컨테이너를 빌드할 수 있습니다.

1개의 좋아요

이 접근 방식에 대해 궁금한 점이 하나 있습니다: 이 설정에서 Rebuild는 어떻게 진행해야 하나요?

@pfaffman님이 추가하신 2개 컨테이너 구성을 처음부터 적용한다고 가정하면, "app"이 두 개, 즉 "data"와 "web_only"가 있게 됩니다.

모든 Rebuild 작업과 관련 처리는 “web_only” 컨테이너로 향하지만, “data” 컨테이너에 대해 별도로 무언가를 해야 하는지, 아니면 "web_only"의 부트스트랩이 이를 처리해 주는 건지 궁금합니다. (테스트를 해보고 있는데, data 컨테이너는 어떤 순간에도 내려가지 않는 것 같아서요.)

저희는 "세 개의 컨테이너"와 리버스 nginx 프록시를 사용합니다:

  1. data
  2. socket1
  3. socket2

현재 socket1(여러분이 'web-only’라고 부르는 것)을 실행 중이라고 가정해 보겠습니다. 그리고 무언가를 재빌드하고 테스트하고 싶다고 합시다. socket1이 실행되는 동안 socket2를 재빌드합니다. 각 컨테이너가 유닉스 도메인 소켓을 사용하므로, 공유 소켓이 서로 다른 파일(위치)에 있기 때문에 두 컨테이너는 동시에 실행될 수 있습니다.

그런 다음, socket2가 빌드되어 준비가 되었다고 가정해 보겠습니다.

저는 이렇게 합니다:

ln -sf /var/discourse/shared/socket2/nginx.http.sock /var/run/nginx.http.sock

이제 socket2에서 라이브로 실행되고 있습니다.

예를 들어 문제가 발생했다면, 저는 그냥 이렇게 하면 됩니다:

ln -sf /var/discourse/shared/socket1/nginx.http.sock /var/run/nginx.http.sock

그러면 socket1에서 실행되던 이전 상태로 되돌아갑니다.

재미로, bash 로그인 프로필에 몇 줄의 코드를 추가하여 서버에 로그인할 때마다 어떤 소켓(컨테이너)이 실행 중인지 항상 알려주도록 했습니다:

Last login: Fri May 15 09:39:39 2020 from 159.192.33.138
srw-rw---- 1 root docker  0 May  5 07:38 /var/run/docker.sock
lrwxrwxrwx 1 root root   44 May 15 09:16 /var/run/nginx.http.sock -> /var/discourse/shared/socket/nginx.http.sock

도움이 되길 바랍니다.

제 생각에는(일반적으로) 이것이 “추천하는 방법”(그리고 프로덕션 환경에서 스테이징이나 개발 환경이 아닌 제 “기본 표준”)이지만, 이것은 제 개인적인 의견이며 YMMV(결과가 다를 수 있음)입니다. 설정에 약간 더 많은 노력이 필요하지만, 프로덕션 환경에서는 그만한 가치가 있다고 생각합니다.


주의사항:


템플릿이 덮어쓰기되어 문제가 될 수 있으므로, 원래의 yml 파일 사본을 안전한 곳에 보관하는 것이 좋습니다. 따라서 우리는 “모든 yml 파일 저장”, “먼저 pull”, 그리고 “yml 파일 확인” 순서로 진행한 후(과도한 신중함으로 인해) bootstrap을 실행하는 경향이 있습니다.

2개의 좋아요

postgres(방금 발생한 것처럼) 또는 redis(지난 6개월 정도 동안 발생함) 업그레이드가 있는 경우를 제외하고는 데이터 컨테이너를 다시 빌드할 필요가 없습니다.

보통은 확인하는 안내문에서 web_only를 app으로 교체하면 됩니다. 관련 노트는 https://support.literatecomputing.com/t/managing-a-two-container-installation/310 에 있습니다.

5개의 좋아요

그러면 이 방식에서는 웹 인스턴스를 두 개 두고, 유휴 상태인 인스턴스를 동일한 데이터로 테스트하는 것이군요? 흥미롭네요. 제가 이 ‘공간을 절약하려는’ 전략을 완전히 정리하는 대로 꼭 한번 시도해 보겠습니다 :stuck_out_tongue:

정말 감사합니다. 글을 처음부터 끝까지 읽었는데, 작은 질문이 하나 있습니다. 여기서 'data’를 의미하신 건 맞나요? (즉, web_only 대신 data를 다시 빌드한다는 의미로, 글에 나와 있는 내용처럼요?) 아니면 제가 놓치고 있는 다른 트릭이 있는 걸까요(예: 큰 업그레이드가 있을 때 두 가지를 합쳐서 실행하고 다시 분리하는 방식 등).

네, 하나의 소켓 기반 컨테이너에서 테스트하고, 추가하고, 재빌드하는 동안 다른 컨테이너에서는 정상적으로 실행됩니다.

네, 두 인스턴스 모두 동일한 데이터 컨테이너를 사용합니다. 데이터 컨테이너는 어떤 웹 애플리케이션과 "대화"하는지 신경 쓰지 않습니다.

기본적으로 컨테이너를 외부에 노출된 TCP/IP 포트가 아닌 공유 유닉스 도메인 소켓으로 실행하는 방법을 이해하면 사실 매우 간단합니다.

저희는 공간이 많이 차지한다고 느끼지 않지만, 디스크 공간이 비싸지 않아서 생산 환경에서 공간 제한을 두고 실행하지 않기 때문이기도 합니다.

1개의 좋아요

자세한 설명 감사합니다. 테스트 환경에서 두 개의 "앱 컨테이너"를 Data One에 연결해 보았는데, 아주 잘 작동했습니다.

하지만 PRD에는 적용할 수 없습니다. PRD에서 Data 컨테이너를 재빌드할 수 없고, 문제를 해결하기 전에 아무것도 변경하고 싶지 않기 때문입니다. discourse@discourse FATAL: terminating connection due to administrator command 오류로 인해 작업이 중단됩니다.