본문으로 바로가기

40

컨테이너

'제 컴퓨터에서는 되는데요'를 끝내는 방법 — 프로그램을 통째로 포장해서 옮긴다

먼저 보면 좋은 강의: 웹 요청의 해부, 지연 시간과 처리량

단계별로 보기

스페이스바로 재생/정지, 좌우 화살표로 단계 이동, R로 처음으로 돌아갑니다.

대기 중1 / 4 단계 — 1단계 — 상자를 봉한다

개발자가 조리법을 넘기면 빌더가 그대로 상자를 봉합니다. 프로그램만이 아니라 실행 환경, 라이브러리, 설정까지 전부 한 상자에 들어갑니다. 이것이 이미지입니다. 여기서 중요한 성질이 생깁니다. 한 번 봉한 상자는 바뀌지 않습니다. 누가 언제 어디서 열어도 안이 똑같습니다.

이 강의를 마치면

  • 컨테이너 이미지를 '실행에 필요한 모든 것을 담은 밀봉 상자'로 설명한다.
  • 이미지를 만드는 곳, 보관하는 곳, 실행하는 곳이 서로 다른 단계임을 구분한다.
  • 같은 이미지에서 찍어낸 복제본이 서로 완전히 같기 때문에 마음대로 늘리고 버릴 수 있음을 설명한다.
  • 컨테이너 하나가 죽어도 서비스가 유지되는 조건을 설명한다.

"제 컴퓨터에서는 되는데요"

소프트웨어 업계에서 가장 유명한 문장입니다. 개발자가 만든 프로그램이 개발자 노트북에서는 잘 돌아가는데, 서버에 올리면 안 됩니다. 왜 그럴까요?

프로그램은 혼자 돌아가지 않습니다. 늘 주변에 기대고 있습니다.

  • 특정 버전의 실행 환경 (예: 파이썬 3.11)
  • 수십 개의 부품 라이브러리, 그것도 특정 버전들
  • 특정 설정 파일, 특정 폴더 구조, 특정 환경 변수

개발자 노트북에는 이것들이 지난 2년간 자연스럽게 쌓여 있습니다. 서버에는 없습니다. 프로그램만 옮기고 프로그램이 기대던 세상은 안 옮긴 것입니다.

이사 비유

이삿짐을 옮긴다고 합시다. 나쁜 방법은 이렇습니다. 가구만 트럭에 싣고 새 집에 가서 "여기도 콘센트 위치가 같겠지"라고 믿는 것입니다. 새 집 콘센트가 다른 벽에 있으면 전등이 안 켜집니다.

컨테이너는 다른 방법을 씁니다. 방을 통째로 상자에 넣습니다. 가구, 콘센트, 벽지, 조명까지 전부 한 상자에 밀봉합니다. 그리고 그 상자를 새 집에 놓습니다. 상자 안은 어디에 놓든 똑같으므로, 새 집이 어떻게 생겼든 상관이 없습니다.

이 상자의 설계도가 이미지이고, 그 상자를 실제로 열어서 돌아가고 있는 상태가 컨테이너입니다. 설계도 하나로 상자를 열 개 만들 수 있듯, 이미지 하나로 컨테이너를 열 개 띄울 수 있습니다.

세 개의 장소, 세 개의 단계

컨테이너를 쓰는 흐름은 항상 이 세 단계입니다. 이 셋을 헷갈리면 나머지가 전부 헷갈립니다.

1. 만든다 (빌드)

"이 프로그램에 파이썬 3.11과 이 라이브러리들을 넣고, 이 설정으로 시작해라"라는 조리법을 적어둡니다. 빌드는 그 조리법대로 상자를 봉하는 작업입니다. 결과물이 이미지입니다.

여기서 결정적인 성질이 하나 생깁니다. 이미지는 한 번 봉하면 바뀌지 않습니다. 누가 언제 어디서 열어도 안이 똑같습니다. 이것을 불변이라고 하고, 컨테이너의 거의 모든 장점이 여기서 나옵니다.

2. 보관한다 (레지스트리)

만든 이미지를 공용 창고에 올립니다. 이 창고를 레지스트리라고 합니다.

왜 창고가 따로 필요할까요? 만드는 곳(빌드 서버)과 쓰는 곳(수십 대의 실행 서버)이 다르기 때문입니다. 창고가 없으면 이미지를 서버마다 일일이 복사해 넣어야 하고, 그러다 보면 어느 서버에 어떤 버전이 들어 있는지 아무도 모르게 됩니다.

창고에는 이름표가 붙습니다. 결제서비스:v37처럼요. 이 이름표 덕분에 "지금 운영에 떠 있는 게 정확히 어느 상자인가"에 항상 답할 수 있습니다. 사고가 났을 때 이 답을 못 하면 조사가 시작조차 안 됩니다.

3. 실행한다 (호스트)

실행 서버가 창고에서 이미지를 내려받아 상자를 엽니다. 이제 컨테이너가 돌아가고, 트래픽을 받기 시작합니다.

같은 이미지를 서버 세 대에서 열면 완전히 똑같은 컨테이너 세 개가 생깁니다. "1번 서버만 좀 이상하네" 같은 일이 원리적으로 일어나지 않습니다. 셋 다 같은 상자니까요.

그래서 뭐가 좋아지는가

이 방식이 바꾸는 것은 "환경 문제 해결"만이 아닙니다. 훨씬 큰 게 따라옵니다.

늘리는 게 쉬워진다

앞선 강의에서 처리량이 부족하면 서버를 늘린다고 배웠습니다. 그런데 서버를 한 대 더 늘리려면 원래는 무엇을 해야 했을까요? 새 기계를 사고, 운영체제를 깔고, 실행 환경을 깔고, 라이브러리를 맞추고, 설정을 복사하고… 며칠이 걸립니다.

컨테이너에서는 창고에서 같은 상자를 하나 더 꺼내 여는 것이 전부입니다. 몇 초입니다. 그래서 트래픽이 몰리면 컨테이너를 열 개 더 띄우고, 빠지면 다시 줄일 수 있습니다.

버리는 게 쉬워진다

더 중요한 것이 이쪽입니다. 컨테이너가 이상해지면 고치지 않습니다. 버리고 새로 엽니다.

전통적인 서버에서는 이럴 수 없었습니다. 서버 한 대 한 대가 소중한 반려동물이라, 이상해지면 로그인해서 손으로 고쳤습니다. 그러다 서버마다 조금씩 달라지고, 결국 아무도 그 서버의 현재 상태를 모르게 됩니다.

컨테이너는 반려동물이 아니라 소모품입니다. 이상하면 죽이고 새 걸 띄웁니다. 새로 띄운 것은 이미지가 불변이므로 처음의 깨끗한 상태 그대로입니다.

다만, 조건이 있다

"죽이고 새로 띄운다"가 성립하려면 컨테이너 안에 소중한 것이 없어야 합니다. 사용자 데이터를 컨테이너 안에 저장했다면, 죽이는 순간 데이터도 사라집니다.

그래서 컨테이너는 상태를 안에 두지 않습니다. 데이터는 데이터베이스에, 파일은 저장소에, 세션은 공유 세션 저장소에 둡니다. 컨테이너 안에는 실행 중인 프로그램만 있습니다. 이 원칙이 지켜져야 비로소 마음 놓고 죽일 수 있습니다.

컨테이너 하나가 죽어도

컨테이너는 언제든 죽습니다. 기계가 고장 나기도 하고, 점검 때문에 내려가기도 하고, 배포하느라 교체되기도 합니다. 죽는 게 정상입니다.

그러니 설계는 "안 죽게 만들기"가 아니라 "죽어도 사용자가 모르게 만들기"가 되어야 합니다. 필요한 것은 두 가지입니다.

  1. 복제본이 여러 개 있을 것. 하나가 죽어도 남은 것들이 받아냅니다.
  2. 앞에서 나눠주는 것이 죽은 컨테이너를 알아차릴 것. 로드밸런서가 계속

"살아 있니?"를 물어보다가, 대답이 없으면 그 컨테이너로 손님을 안 보냅니다.

둘 중 하나만 빠져도 소용이 없습니다. 복제본이 열 개여도 로드밸런서가 죽은 걸 모르면 손님의 10%는 계속 죽은 문 앞으로 갑니다.

직접 해보세요

아래 과제에서 컨테이너 한 대가 트래픽을 받고 있다가 10초 뒤에 죽습니다. 지금 구조라면 그 순간 서비스가 끝납니다. 같은 이미지에서 찍어낸 복제본을 더 띄우고 로드밸런서를 앞에 두어, 하나가 죽어도 사용자가 눈치채지 못하게 만드세요.

이 강의가 단순화한 것

교육용 모형은 언제나 무언가를 생략합니다. 무엇을 생략했는지 아는 것도 학습의 일부입니다.

전제한 것

  • 이 강의는 등록된 부품만으로 컨테이너 세계를 흉내 낸다. '이미지 빌더'와 '실행 호스트'는 서버 부품으로, '레지스트리'는 저장소 부품으로 대신 표현했다.
  • 이미지를 내려받는 시간과 컨테이너가 뜨는 시간은 실제로는 수 초에서 수십 초가 걸리지만, 여기서는 흐름을 보여주기 위해 짧게 표현했다.
  • 컨테이너 안에는 사용자 데이터가 없다고 가정한다. 상태를 가진 컨테이너는 이 강의의 규칙이 그대로 적용되지 않는다.
  • 씬의 시간과 숫자는 설명을 위해 고른 값이며 실제 측정값이 아니다.

다루지 않은 것

  • 이미지 레이어와 캐시, 이미지 크기 최적화
  • 네임스페이스·cgroup 등 격리를 실제로 구현하는 운영체제 기능
  • 쿠버네티스 같은 오케스트레이터의 스케줄링, 자동 복구, 오토스케일링 정책
  • 컨테이너와 가상머신의 차이, 이미지 서명과 취약점 스캔
  • 영속 볼륨과 상태를 가진 컨테이너(StatefulSet 등)

더 읽을거리: The Twelve-Factor App · OCI Image Format Specification

이제 직접 만들어 보세요

읽어서 아는 것과 만들어서 아는 것은 다릅니다. 컴포넌트를 배치하고 트래픽을 흘려서 실제로 동작하는지 확인해 보세요.

실습 과제 열기

이 강의에 물어보기

하루 5번

이 강의 본문만 근거로 답합니다. 강의에 없는 내용은 지어내지 않습니다.