OS는 하나지만 경계만 여럿입니다.
컨테이너는 단순한 가벼움이 아니라 다른 경계가 됩니다.
그 경계를 만드는 것이 네임스페이스와 cgroup입니다.

처음 컨테이너를 접했을 때의 잘못된 질문

컨테이너를 처음 접했을 때, 저는 자연스럽게 VM과 비교하기 시작했습니다. 그리고 이런 식으로 정리했습니다.

"VM은 OS까지 통째로 올리니까 무겁고, 컨테이너는 OS를 공유하니까 가볍다."

틀린 말은 아닙니다. 하지만 이 비교에서 출발하면 정작 중요한 부분을 놓치고 있었습니다. 가벼움은 결과이지, 컨테이너의 본질이 아니기 때문입니다.

파고들다 보니 질문이 바뀌었습니다.

"VM과 컨테이너는 무게가 다른 게 아니라, 경계를 어디에 그었느냐가 다른 것 아닐까?"

이 질문에서 출발하면 컨테이너의 구조가 훨씬 선명하게 보이기 시작했습니다.


VM과 컨테이너: 경계의 위치가 다릅니다

2편에서 VM은 OS 단위로 경계를 세운다고 정리했습니다. 게스트 OS 전체가 격리의 단위였습니다. 커널이 분리되기 때문에 한 VM에서 발생한 사고가 다른 VM으로 번지지 않았습니다. 강한 격리였지만, 그만큼 운영 단위가 컸습니다.

컨테이너는 다른 선택을 합니다.

OS 커널은 공유합니다. 대신 그 위에서 돌아가는 프로세스(집합)를 격리합니다.

격리의 단위가 머신(OS)에서 프로세스로 내려온 것입니다. 커널은 하나지만, 각 컨테이너는 자신만의 독립된 세계가 있는 것처럼 동작합니다.

여기서 제가 막혔던 지점이 있습니다.

"커널을 공유하면 격리가 제대로 되는 건가? VM보다 경계가 약한 것 아닌가?"

이 의문을 풀려면 컨테이너가 경계를 어떻게 만드는지 들여다봐야 했습니다.


경계를 만드는 첫 번째 도구: 네임스페이스(Namespace)

컨테이너의 격리는 네임스페이스라는 Linux 커널 기능에서 시작됩니다.

네임스페이스가 하는 일은 단순합니다. 프로세스가 볼 수 있는 범위를 제한합니다.

예를 들어 컨테이너 A 안에서 실행 중인 프로세스는 이렇게 동작합니다.

  • 자신의 PID가 1번이라고 알고 있습니다. (실제로는 호스트에서 다른 번호입니다.)
  • 자신만의 네트워크 인터페이스가 있다고 알고 있습니다.
  • 자신만의 파일 시스템 루트(/)를 가진 것처럼 동작합니다.

이것은 2편에서 본 VM의 게스트 OS와 비슷한 구조입니다. 차이는 커널이 분리된 것이 아니라, 커널이 제공하는 뷰(View)만 분리됐다는 점입니다.

프로세스는 자신이 격리된 세계에 있다고 믿습니다. 실제로는 같은 커널 위에 있지만, 네임스페이스가 그 뷰를 잘라냅니다. 저는 이것이 Virtual Memory에서 봤던 구조와 같다고 느꼈습니다. 주소가 가짜였던 것처럼, 여기서는 프로세스가 보는 세계가 가짜입니다.

네임스페이스는 컨테이너의 경계(Boundary) 입니다.

커널 하나에 여러 개의 뷰


경계에 의한 자원 배분: cgroup

네임스페이스로 프로세스가 보는 세계를 격리했다고 해서 끝이 아니었습니다. 여기서 또 막혔습니다.

"보이는 것만 격리하면 자원은 어떻게 나누나? 컨테이너 하나가 CPU를 다 써버리면?"

이 문제를 해결하는 것이 cgroup(Control Group)입니다.

cgroup은 프로세스 집합이 사용할 수 있는 자원의 양을 제한합니다.

  • 이 컨테이너는 CPU의 몇 %까지만 사용할 수 있다.
  • 이 컨테이너는 메모리를 얼마까지만 점유할 수 있다.
  • 이 컨테이너의 네트워크 대역폭은 여기까지다.

2편에서 하이퍼바이저가 VM들의 자원 충돌을 정책으로 조정했던 것처럼, cgroup은 컨테이너 수준에서 같은 역할을 합니다.

cgroup은 컨테이너의 정책(Policy) 입니다.


컨테이너에서 경계 + 매핑 + 정책

이전 편들에서 반복됐던 프레임을 컨테이너에 대입하면 이렇게 정리됩니다.

  • 경계(Boundary): 네임스페이스가 프로세스가 볼 수 있는 범위를 자릅니다.
  • 매핑(Mapping): 컨테이너 안의 가상 경로, 네트워크, PID가 호스트의 실제 자원과 연결됩니다.
  • 정책(Policy): cgroup이 각 컨테이너의 자원 사용량을 제한하고 조정합니다.

VM에서는 하이퍼바이저가 이 세 가지를 담당했습니다. 컨테이너에서는 Linux 커널 자체가 이 역할을 수행합니다. 별도의 하이퍼바이저 레이어 없이, 커널 기능만으로 경계와 매핑과 정책이 작동합니다. 이것이 컨테이너가 가벼운 이유입니다. 하지만 본질은 가벼움이 아니라, 경계를 어디에 그었느냐의 차이입니다.


"약한 격리 아닌가?"라는 질문에 대한 답

컨테이너가 커널을 공유한다는 사실은 분명히 VM보다 격리가 약하다는 의미이기도 합니다. 커널 취약점이 있으면 컨테이너 경계가 뚫릴 수 있습니다. VM이라면 커널이 분리되어 있어서 같은 상황에서 더 강하게 막아냅니다.

이 트레이드오프는 실제로 존재합니다. 컨테이너는 격리를 포기하고 효율을 택한 것이 아닙니다. 격리의 단위를 바꾼 것입니다.

VM은 "다른 머신"을 만듭니다. 컨테이너는 "같은 머신 안의 다른 세계"를 만듭니다.

어느 쪽이 옳다는 이야기가 아닙니다. 운영하려는 단위가 무엇이냐에 따라 적합한 경계가 달라집니다.


요약

  • 컨테이너는 VM보다 가벼운 기술이 아니라, 경계를 OS에서 프로세스로 내린 기술입니다.
  • 네임스페이스가 프로세스가 볼 수 있는 범위를 격리합니다. (경계)
  • 컨테이너 안의 가상 자원은 호스트의 실제 자원과 매핑됩니다. (매핑)
  • cgroup이 컨테이너별 자원 사용량을 제한하고 조정합니다. (정책)
  • 커널을 공유하기 때문에 격리 수준이 다르며, 이는 트레이드오프입니다.

다음 편: Docker, 경계를 배포 단위로 굳히다

컨테이너라는 경계가 만들어졌지만, 그것을 어떻게 묶고 옮기고 재현할 것인가는 별개의 문제였습니다. Docker는 컨테이너에 이미지라는 포장을 씌워 배포 경험을 표준화했습니다. 다음 편에서는 Docker가 컨테이너의 어떤 문제를 풀었는지 따라가보겠습니다.

다음글

 

Virtual Machine은 도대체 무엇을 가상화했을까? - 가상화의 역사 2

VM은 OS가 보는 컴퓨터를 만듭니다.하이퍼 바이저가 매핑, 정책으로 운영합니다.그 결과, OS 단위 격리가 만들어집니다.1. 하드웨어 가상화라는 말은 어디까지를 포함할까?하드웨어 가상화라는 표

chessire.tistory.com

 

VM은 OS가 보는 컴퓨터를 만듭니다.
하이퍼 바이저가 매핑, 정책으로 운영합니다.
그 결과, OS 단위 격리가 만들어집니다.

1. 하드웨어 가상화라는 말은 어디까지를 포함할까?

하드웨어 가상화라는 표현을 들으면, 흔히 CPU나 메모리 같은 부품을 소프트웨어로 그대로 재현하는 기술을 떠올리게 됩니다. 하지만 Virtual Machine(VM)을 정리하다 보니 제가 처음에 잘못된 질문을 하고 있었다는 걸 깨달았습니다. “VM은 도대체 하드웨어의 무엇을 가상화했는가?”입니다.

정리를 해보면 다음 문장에 닿게 됩니다. VM이 가상화한 대상은 CPU나 메모리 자체가 아니라, 운영체제가 하드웨어라고 전제하고 기대하는 인터페이스 전체입니다. 정확히 말하면 VM은 OS가 보게 되는 한 대의 컴퓨터를 가상화합니다.

이 구조 덕분에 게스트 OS는 가상 환경임을 전제로 하지 않아도, 지금 물리 머신 위에 있다는 규약 하에 부팅하고, 드라이버를 로드하며, 스케줄링과 메모리 관리를 수행합니다.


2. 운영체제가 말하는 하드웨어는 부품이 아니라 규약입니다

여기서 중요한 전제를 하나 고정하고 가야 합니다. 운영체제 입장에서 하드웨어는 손에 잡히는 부품 목록이 아니라, 약속된 규약(인터페이스)들의 집합입니다.

  • CPU 인터페이스: 특권 명령 실행, 인터럽트 처리, 유저/커널 모드 전환 규칙
  • 메모리 인터페이스: 주소 공간 접근 방식, 페이지 폴트 같은 이벤트 처리
  • I/O 인터페이스: 디스크·네트워크 장치에 데이터를 쓰고 읽는 방식(DMA 등)
  • 부팅 인터페이스: 전원이 켜진 뒤 어느 지점에서 실행을 시작할지에 대한 약속

VM은 이 규약 묶음을 게스트 OS마다 한 세트씩 제공해, OS가 독립된 머신을 가진 것처럼 동작하게 만듭니다.


3. 그렇다면 현실 자원(CPU/메모리/디스크)은 어떻게 공유될까?

게스트 OS마다 “내가 주인인 머신”이 존재한다면, 그 규약을 현실의 물리 자원과 연결해 줄 구성 요소가 필요합니다. 여기서 하이퍼바이저(Hypervisor)가 등장합니다.

0편에서 정의했던 가상화의 3요소를 대입하면 VM의 구조는 이렇게 압축됩니다.

VM = OS 단위의 추상화 경계 + 현실 자원을 연결하는 매핑 + 충돌을 조정하는 정책

 

이 프레임을 잡고 나서야 VM이 왜 그렇게 설계됐는지가 비로소 납득됐습니다


4. VM이 세운 경계: 프로세스가 아니라 OS

VM이 제공하는 격리는 프롤로그에서 본 가상 메모리와 격리 단위가 다릅니다.

가상 메모리가 프로세스 단위의 경계를 만들었다면, VM은 OS 단위의 경계를 세웁니다. 커널 자체가 분리되기에, 한 VM에서 발생한 커널 패닉이나 보안 사고가 다른 VM으로 번질 가능성이 크게 줄어듭니다.

즉, VM의 운영 단위는 개별 프로그램이 아니라 머신(OS 포함) 전체가 됩니다. 이 경계 덕분에 서로 다른 OS를 한 서버에서 동시에 운영할 수 있게 되었습니다.


5. 경계가 생기면, 현실과 연결하는 매핑이 필요합니다

게스트 OS가 보는 세계가 실제 자원을 쓰려면 연결 표(매핑 테이블)가 작동해야 합니다. VM의 핵심 동작은 게스트의 관점을 현실의 물리 자원에 맞춰 지속적으로 매핑하는 일입니다.

  • 게스트가 보는 4개의 CPU 코어는 실제 CPU의 시간 조각(Time Slice)에 매핑됩니다.
  • 게스트가 물리 메모리라고 믿는 주소는 호스트 메모리의 특정 영역으로 재매핑됩니다.
  • 게스트의 디스크 쓰기 요청은 물리 장치 자체가 아니라, 호스트의 파일 또는 가상 장치 모델로 연결될 수 있습니다.

여기서 핵심은 자원의 복제가 아니라 연결입니다. 현실 자원은 하나지만, 매핑을 통해 여러 게스트에게 각자의 자원처럼 보이게 만드는 구조입니다.


6. 여러 VM이 동시에 돌면, 정책이 필요합니다

매핑이 가능해졌다고 해서 끝나지는 않습니다. 여러 VM이 동시에 자원을 사용하면 충돌은 피할 수 없습니다. 그래서 하이퍼바이저는 정책(Policy)을 집행합니다.

  • 어떤 VM에게 CPU 시간을 우선적으로 배분할 것인가(스케줄링)
  • 특정 VM이 메모리를 어디까지 점유할 수 있게 할 것인가(제한 및 회수)
  • 네트워크 대역폭을 어떻게 나눌 것인가(QoS 등)

이 구조를 따라가다 보니, 0편에서 세운 공식이 VM에서도 똑같이 나타났습니다.

VM은 OS가 보는 한 대의 컴퓨터를 만든다.


7. “하드웨어를 속인다”보다 정확한 표현은 “규약을 만족시킨다”입니다

저도 처음엔 하드웨어를 속이는 기술이라고 생각했는데, 직접 파고들어 보니 더 정확한 표현이 따로 있었습니다. OS의 기대를 만족시키는기술이라고 쓰는 편이 더 정확해보입니다.

게스트 OS는 특정 명령을 실행하면 하드웨어가 정해진 방식으로 응답할 것이라는 규약을 전제로 설계되어 있습니다. 하이퍼바이저는 그 규약이 안전하게 성립하도록, 필요할 때 실행을 트랩(Trap)으로 전환해 처리하거나, 하드웨어 지원(VT-x 등)을 통해 가능한 범위에서 직접 실행되도록 구성합니다.

OS가 믿는 머신의 규약을 재현하고, 현실 자원으로 매핑하며, 정책으로 조정한다.
이것이 VM의 핵심입니다.


8. 다음 편으로 넘어가는 질문

VM은 OS라는 큰 경계를 세움으로써 강한 격리를 얻었습니다. 다만 운영 단위가 OS인 만큼, 상황에 따라 오버헤드가 생깁니다. 그렇다면 이런 질문이 가능합니다.

“격리는 유지하면서, OS까지 통째로 포함하지 않고 더 가볍게 만들 순 없을까?”

이 고민이 바로 컨테이너(Container)의 시작점입니다. 경계가 바뀌면, 매핑과 정책도 바뀝니다. 다음 편에서는 경계가 머신에서 프로세스 쪽으로 이동했을 때 무엇이 달라지는지 살펴보겠습니다.


9. 요약 (5줄)

  • VM은 물리 부품이 아니라 하드웨어 인터페이스 규약을 가상화합니다.
  • 운영 단위(경계)를 OS 전체로 잡기 때문에 강한 격리 능력을 갖습니다.
  • 게스트의 자원 요청은 하이퍼바이저의 매핑을 거쳐 물리 자원으로 연결됩니다.
  • 자원 공유의 질서는 하이퍼바이저가 집행하는 정책에 의해 유지됩니다.
  • VM은 OS의 기대를 만족시키는 인터페이스 재현 레이어입니다.

이전글

 

첫 번째 가상화 레이어 Virtual Memory - 가상화의 역사 1

주소는 가짜입니다.매핑과 정책이 경계를 만듭니다.가상화는 여기서 시작됩니다.1. “Virtual Memory가 가상화 맞아요?”이 시리즈를 시작하며 저는 “VM → 컨테이너 → Docker → Kubernetes” 흐름을

chessire.tistory.com

다음글

 

OS-Level Virtualization - 가상화의 역사 3

OS는 하나지만 경계만 여럿입니다.컨테이너는 단순한 가벼움이 아니라 다른 경계가 됩니다.그 경계를 만드는 것이 네임스페이스와 cgroup입니다. 처음 컨테이너를 접했을 때의 잘못된 질문컨테이

chessire.tistory.com

 

OS까지 쪼개는 가상화
경계를 세우는 순간 따라오는 매핑과 정책
컨테이너, Docker, Kubernetes

1. 하드웨어 가상화는 도대체 무엇을 가상화한 걸까?

하드웨어 가상화라는 단어를 처음 접하면 보통 비슷한 이미지를 떠올리게 됩니다. CPU나 메모리를 교묘하게 속이는 기술 같은 느낌 말입니다. 하지만 이 개념을 파고들다 보면 정작 중요한 질문은 다른 곳에 있다는 것을 알게 됩니다. 대체 무엇을 하드웨어처럼 보이게 만들고 싶었던 걸까?

저도 처음엔 복잡하게 접근했는데, 파고들수록 오히려 단순한 질문으로 좁혀졌습니다. 바로 한 대의 물리 머신 위에 여러 대의 머신이 있는 것처럼 보이게 만드는 구조입니다. 여기서 말하는 머신은 단순한 프로그램 하나가 아니라, OS까지 포함한 실행 환경 전체를 의미했습니다.

결국 하드웨어 가상화라는 말은, 사실상 OS 단위의 경계를 하나 더 만드는 작업에 가까웠습니다. 근데 여기서 저는 한 가지가 걸렸습니다.

OS를 통째로 올리는 건 너무 무겁지 않았을까?
그냥 프로세스만 잘 돌리면 충분했을 텐데, 왜 굳이 이런 선택을 했을까?

그래서 저는 경계를 어디에 그었는지부터 먼저 따라가봤습니다.


2. 하드웨어 가상화를 왜 쓰게 됐을까?

단순히 “필요해서 썼다”는 설명은 큰 도움이 되지 않았습니다. 구체적으로 어떤 불편함이 우리를 OS까지 통째로 가두는 구조로 이끌었는지 들여다볼 필요가 있었습니다. 그 불편함의 장면들을 복기해 보면 세 가지 키워드가 선명해집니다.

(1) 같이 돌리면 같이 망가지는 순간 (Isolation)

서버 한 대에서 여러 서비스를 돌리다 보면, 한쪽의 자원 폭주가 전체 시스템을 흔들어놓는 장면을 흔히 봅니다. 프로세스 수준에서 주의를 기울이는 것만으로는 해결되지 않는 결함들이 존재했습니다. 그래서 아예 운영 단위를 OS 레벨에서 격리해버리는 선택이 매력적으로 다가왔던 겁니다.

(2) 환경이 다르면 생기는 끝없는 변수 (Illusion)

“내 로컬 PC에서는 잘 되는데요”라는 말이 운영 환경에서 통하지 않을 때의 당혹감은 익숙합니다. OS 버전, 라이브러리, 미세한 설정 차이가 결과값을 바꿨습니다. 결국 프로그램만 옮기는 게 아니라, 그 프로그램이 믿고 있는 환경 자체를 복제해 일관된 실행 환경을 만들어낼 필요가 있었습니다.

(3) 하드웨어가 바뀌면 위쪽도 흔들리는 구조 (Indirection)

서버를 교체하거나 스토리지 구성이 바뀔 때마다 상위 서비스가 영향을 받는다면 운영의 유연성은 떨어질 수밖에 없습니다. 인프라와 서비스 사이의 직접적인 연결을 끊어줄 간접화의 층이 절실해진 지점이었습니다.

그런데 여기서 또 막혔습니다. 격리(Isolation)만 잘하면 되는 것 아닐까? 왜 환상(Illusion)과 간접화(Indirection)는 항상 세트처럼 따라다니는 걸까?


3. 왜 Illusion / Isolation / Indirection은 늘 한 세트일까?

이건 철학의 문제라기보다, 구조를 따라가다 보면 자연스럽게 그렇게 보였습니다. 격리를 위해 경계를 긋는 순간, 연쇄적인 반응이 시작되기 때문입니다.

격리를 하려면 명확한 경계(Boundary)를 세워야 합니다.

경계를 세우고 나면, 그 경계 안에서 보이는 자원과 실제 물리 자원을 연결하는 매핑(Mapping)이 필요해집니다. (이 연결을 상위 레이어 관점에서 보면 환상이 되고, 구조 관점에서 보면 간접화가 됩니다.)

매핑이 생겨 자원을 공유하기 시작하면, 이를 공정하게 나누기 위한 정책(Policy)이 필요해집니다.

격리라는 목표를 세우는 순간, 환상과 간접화, 그리고 정책이 따라오는 것은 매우 자연스러운 흐름이었습니다. 이 과정을 거치면 가상화의 정체가 하나의 명료한 문장으로 정리됩니다.

가상화는 결국 경계(Boundary) + 매핑(Mapping) + 정책(Policy)으로 굴러가는 구조였습니다.


4. VM에서 “경계 + 매핑 + 정책”은 어떤 모습이었을까?

이 구조를 구체적인 가상 머신(VM)의 모습에 대입해 보면 더 명확해집니다.

(1) 경계(Boundary): 운영 단위를 OS로 설정

하드웨어 가상화는 격리의 단위를 프로세스가 아닌 머신(OS 포함)으로 잡았습니다. 무겁다는 단점이 있지만, 경계가 매우 선명하고 강력하다는 확실한 이점을 얻었습니다.

(2) 매핑(Mapping): 가상 자원을 현실 자원에 연결

VM 안의 CPU, 메모리, 디스크가 실제로 작동하려면 물리 자원과의 연결이 필요합니다. 가상 디스크가 실제 스토리지의 어디에 위치하는지, 네트워크 패킷이 어떤 물리 스위치로 나가는지를 관리하는 식입니다. 이 매핑(연결)을 유지하는 여러 구성요소가 가상화를 지탱합니다.

(3) 정책(Policy): 공유를 위한 의사결정

자원을 나눠 쓰는 순간 결정해야 할 것들이 폭발합니다. CPU 시간을 어떻게 쪼갤지, 메모리 할당량을 어디까지 허용할지, 특정 VM의 폭주를 어떻게 제한할지 같은 정책들이 시스템의 핵심이 됩니다.

결국 하드웨어 가상화는 단순한 기술이 아니라, 공유를 안전하게 운영하기 위한 제어/관리 계층에 가까웠습니다.


가상화는 경계를 만들고, 매팡하고, 정책으로 운영한다.

5. VM에서 Container, Kubernetes로 이어지는 흐름: 경계의 이동

VM을 이렇게 구조적으로 이해하고 나면, 컨테이너나 쿠버네티스로 이어지는 역사가 단순히 더 가벼운 기술의 등장이 아님을 깨닫게 됩니다. 핵심은 경계의 위치가 어디로 움직였느냐에 있었습니다.

  • Virtual Memory: 경계가 주소 공간(프로세스)에 생겼습니다.
  • VM(하드웨어 가상화): 경계를 머신(OS) 전체로 끌어올려 강력한 격리를 얻었습니다.
  • Container: 경계를 다시 프로세스(집합) 단위로 내려 효율성을 챙겼습니다.
  • Docker: 그 경계를 배포 가능한 패키지로 굳혀 배포 경험을 표준화하고 단순화했습니다.
  • Kubernetes: 경계를 개별 서버를 넘어 클러스터 운영 단위로 확장했습니다.

결국 가상화의 역사는 운영 단위를 무엇으로 잡느냐가 이동해 온 과정이었습니다. 기술이 완전히 바뀐 것이 아니라, 경계/매핑/정책이라는 틀을 유지한 채 운영의 단위만 옮겨 다니며 진화해 온 역사였던 셈입니다. 이 흐름을 파악하고 나면 현대 인프라 기술들의 연결 고리가 비로소 매끄럽게 보이기 시작합니다.


6. 요약 및 다음 편 예고

오늘 정리해 본 내용을 다섯 줄로 요약하면 이렇습니다.

  • 하드웨어 가상화는 OS(머신) 단위로 경계를 세우는 방식이었습니다.
  • 격리를 시작하면 매핑이 필요해지고, 자연스럽게 정책이 따라붙습니다.
  • 따라서 가상화는 “경계 + 매핑 + 정책”의 구조로 이해하는 것이 가장 깔끔합니다.
  • VM에서 쿠버네티스까지의 발전은 이 경계가 이동해 온 역사입니다.
  • 이 패턴을 이해하면 어떤 가상화 기술도 구조적으로 분석할 수 있습니다.

다음 편에서는 이 모든 패턴이 처음으로 가장 선명하게 나타났던 지점을 살펴보려 합니다. 바로 Virtual Memory입니다. 주소 공간이라는 첫 번째 가상화 레이어가 어떤 힌트를 주었는지 함께 따라가 보겠습니다.

 

다음 글

 

첫 번째 가상화 레이어 Virtual Memory - 가상화의 역사 1

주소는 가짜입니다.매핑과 정책이 경계를 만듭니다.가상화는 여기서 시작됩니다.1. “Virtual Memory가 가상화 맞아요?”이 시리즈를 시작하며 저는 “VM → 컨테이너 → Docker → Kubernetes” 흐름을

chessire.tistory.com

 

인프라는 문서가 아니라 실행 가능한 구성이다.
Compose로 팀의 개발 환경을 동일하게 만든다.

개발자가 로컬에서 겪는 환경 차이를 없애기 위해,
운영 인프라를 Docker Compose로 고정하는 방식을 사용합니다.

 

이를 통해 팀원들의 개발 환경을 동일하게 맞출 수 있고,
운영 인프라에서 발생할 수 있는 문제를
localhost 환경에서 미리 재현하고 대응할 수 있습니다.

 

저의 경우, 다음과 같은 구성을 로컬에서 세팅했습니다.

  • Oracle 1대
  • Redis 3대(Cluster, Master - Slave)
  • Backend 3대

 

docker-compose.oracle.yml

docker-compose.oracle.yml
0.00MB

oracle yml 파일

01-create-user.sql
0.00MB

oracle init 파일

 

Oracle은 로컬에 직접 설치하기 번거롭고,

버전 및 초기 설정 차이가 발생하기 쉬워 Docker Compose로 고정했습니다.

 

컨테이너 기동 시 초기 SQL을 실행하도록 구성하여,

계정 생성까지 자동화합니다.

 

docker-compose.redis.yml

docker-compose.redis.yml
0.00MB

redis cluster yml 파일

 

Redis는 각 Container에 단일 Redis 인스턴스를 두고,
Master 3개로 Cluster를 구성하여 Docker Compose로 고정했습니다.

각 Master에는 서로 다른 Container의 Slave를 연결하여,
A PC의 Master – B PC의 Slave,
B PC의 Master – C PC의 Slave,
C PC의 Master – A PC의 Slave 형태로
교차(replica) 구조를 구성했습니다.

이를 통해 특정 Redis 노드가 Down되더라도
동일 머신에 Master와 Slave가 동시에 장애를 일으키는 상황을 피하도록 설계했습니다.

 

docker-compose.dev.yml

docker-compose.dev.yml
0.00MB

backend yml 파일

default.conf
0.00MB

nginx yml 파일

 

이제 backend를 세팅하겠습니다.

 

Backend는 NestJS 기반 Node App 3개를 띄우고,
Nginx를 통해 Load Balancing을 구성했습니다.

 

각 인스턴스는 동일 이미지로 실행하며,
Nginx는 upstream으로 3대를 묶어 라운드로빈으로 분산합니다.

외부에서는 Nginx만 바라보도록 구성해
로컬에서도 수평 확장 형태를 재현했습니다.

 

맺은말

이 구성은 로컬에서 운영 인프라를 최대한 유사하게 재현하기 위한 예시입니다.

환경 차이로 인한 문제를 줄이고,
인프라 변경이나 장애 상황을 사전에 확인하는 용도로 사용하고 있습니다.

각 구성은 필요에 따라 단순화하거나 확장해도 무방합니다.

symstore 사용법


위치 : C:\Program Files (x86)\Windows Kits\8.1\Debuggers\x64


symstore add /r /f C:\Some\Directories\*.* /s \\SymbolServer\ /t "myapp" /v "build 101" /c "20180312"


Visual studio options



Tools / Options / Debugging / Symbols

symbol_server 추가후 OK버튼 클릭


준비물

Jenkins서버

SVN서버(저장소)


* 필자의 SVN서버와 Jenkins 서버는 서로 다르기 때문에 그것을 중심으로 설명하겠습니다.


1. Jenkins를 커맨드라인에서 사용하기

https://wiki.jenkins-ci.org/display/JENKINS/Jenkins+CLI 에 들어가보면 Jenkins.war안에 Jenkins-CLI.jar가 있다고 하는데.... 아무리 찾아봐도 없습니다. 그래서 Jenkins 설정창을 들어가보니 떡하니 위에서 8번째에 존재하는 Jenkins CLI라는 항목!

들어가서 Jenkins CLI 설명을 보고 svn서버에서 Jenkins-CLI.jar를 다운받습니다.

'BackEnd · DevOps > CI · CD' 카테고리의 다른 글

Symbol Server 설정  (0) 2018.03.12
젠킨스에서 IOS빌드 및 배포  (0) 2015.02.26
Jenkins에서 Unity 빌드하기  (1) 2015.01.26

젠킨스의 해당 잡에서 build에 excute shell을 추가합니다.


xcode 빌드는 xcodebuild라는 녀석을 통해합니다.

xcodebuild는 xcode에서 commandline tool을 설치하면 됩니다. (preference에 있습니다.)

xcodebuild -project "프로젝트의 상대경로.xcodeproj" -configuration "Release or Debug or anything else" -scheme 스킴이름 clean archive -archivePath "생성할 xcarchive 상대경로.xcarchive"

xcodebuild -exportArchive -exportFormat ipa -archivePath "위에서 생성한 xcarchive 상대경로.xcarchive" -exportPath "생성할 ipa 상대경로.ipa" -exportProvisioningProfile "프로비져닝프로파일 이름"


프로비져닝 제대로 세팅을 안하면 ipa가 설치되는 것처럼 보여도 마지막에 실패하는 문제가 발생합니다.


ipa를 웹에서 설치하도록 하는 방법


1. plist를 생성

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">

<plist version="1.0"> <dict>

<key>items</key>

<array><dict>

<key>assets</key>

<array><dict>

<key>kind</key><string>software-package</string>

<key>url</key><string>ipa URL</string>

</dict></array>

<key>metadata</key>

<dict>

<key>bundle-identifier</key><string>bundle identifier</string>

<key>bundle-version</key><string>bundle version</string>

<key>kind</key><string>software</string>

<key>title</key><string>product name</string>

</dict>

</dict></array>

</dict></plist>

1. 위와 같이 plist파일을 생성

2. ipa URL을 입력

3. bundle identifier를 입력

4. bundle version를 입력

5. product name을 입력

6. 생성한 plist파일을 https서버에 업로드(ex : 드랍박스, etc...)


2. 아이폰의 사파리에서 아래의 주소를 입력합니다.

itms-services://?action=download-manifest&url=https://dl.dropboxusercontent.com/s/aej12jdnkc/myapp.plist

(plist파일만 https서버 상에 올라가있으면 됩니다.)

목적 : CI를 통해  Unity빌드를 자동화 한다.


필요한 것

Unity pro

SVN

Jenkins

JDK(jenkins설치시 필요)

Jenkins의  unity build plugin


진행 과정

0. 젠킨스 설치

0-1. jenkins를 다운로드 후, 설치를 하면 Applications(응용 프로그램)/Jenkins/jenkins.war가 생성된다.

0-2. terminal에서 jenkins.war의 디렉터리로 이동 후, $java -jar jenkins.war 명령을 실행한다.

0-3. 사파리에서 localhost:8080으로 접속한다.

 - Default port번호는 8080 변경하고 싶으면 --httpPort=번호 로 바꿔준다.

1. 시스템 설정을 한다.

1-1. JDK Installations

 - mac에서 JAVA_HOME은 JDK설치 후 /usr/libexec/java_home을 실행하면 알 수 있음

1-2 ANT Installations(있으니까 했음, Maven연동은 안한)

1-3 unity build plugin을 설치하면 Maven아래쪽에 Unity3d탭이 생김 Untiy installations를 등록

 - mac에서 발생하는 Not Unity3d home directory /Applications/Unity 에러가 뭔지 모르겠음.

 => 발견 : Installation directory를 /Applications/Unity/Unity.app으로 해야됨

2. Configure Global Security

2-1. Enable security 활성화

2.2. Security Realm의 Access Control에서 Jenkins'own user database 활성화 및 사용자의 가입 허용 활성화

2.3 Autorization에서 Project-based Matrix Authorization Stratgy 활성화

 => 젠킨스 세팅 완료


3. 새로운 Item(Job 등록)

3-1. 새로운 Item클릭 후, Freestyle project 생성

3-2. 소스 코드 관리에서 Subversion선택

 - Unable to access svn://blah.blah.blah.blah/svn/what/what : svn:E200015:No credential to try. Authentication failed(show details) (Mabe you need to enter credential?) 에러 발생시에는 enter credential을 클릭 후, subversion Authentication을 등록하면 된다.

3-3. Build탭에서 Invoke Unity3d Editor에서 Unity3d installation name을 아까 시스템 설정창에서 등록한 유니티로 설정.

3-4. Editor command line arguments을 채움

 - -quit -batchmode -project Path "/Path/path/Path/path/path" -executeMethod className.methodName -output  "%WORKSPACE%\output" -Define PREDEFINES로 했음

- http://docs.unity3d.com/Manual/CommandLineArguments.html 참조


이제 실행시키면 됨.


빌드 시, 문제 발생

해당 빌드 콘솔에서 FAILED TO establish the default connection to the WindowServer가 뜨고 아무것도 안되는 경우,

sudo launchctl unload /Library/LaunchDaemons/org.jenkins-ci.plist

sudo mv /Library/LaunchDaemons/org.jenkins-ci.plist /Library/LaunchAgents/

를 입력하면 젠킨스가 종료됩니다. 이후, 젠킨스를 실행해주면 잘 됩니다.



using UnityEngine;

using System;

using System.IO;

using System.Collections;

using System.Collections.Generic;

using UnityEditor;


public class CommandBuild

{

static Dictionary<string, string> arguments = new Dictionary<string, string>();

static void InitCommandLineArgs()

{

string[] commandArgs;

if (editorBuild != null)

commandArgs = editorBuild.Split(' ');

else

commandArgs = Environment.GetCommandLineArgs();


for (int i = 0; i < commandArgs.Length; i++)

{

if (commandArgs[i].Length > 1 && commandArgs[i].StartsWith("-"))

{

string key = commandArgs[i].Substring(1);

if (arguments.ContainsKey(key) == false)

{

string value = null;

if (i + 1 < commandArgs.Length)

value = commandArgs[i + 1];

arguments.Add(key, value);

}

}

}

}

public static void BuildAndroid()

{

InitCommandLineArgs();


string define = GetValue("Define");

if (define != null)

PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, define);


string outputFileName = GetValue("outputFileName");

string savepath = string.Format("{0}/../{1}.apk", Application.dataPath, outputFileName);

PlayerSettings.Android.keystoreName = "keystoreName.keystore";

PlayerSettings.Android.keyaliasName = "keyaliasName";

PlayerSettings.Android.keystorePass = "keystorePass";

PlayerSettings.Android.keyaliasPass = "keyaliasPass";


Build(savepath, BuildTarget.Android);

}


public static void BuildiOS()

{

InitCommandLineArgs();

string define = GetValue("Define");

if (define != null)

PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.iPhone, define);

string outputFileName = GetValue("outputFileName");

string savepath = string.Format("{0}/../"+outputFileName, Application.dataPath);


Build(savepath, BuildTarget.iPhone);

}




이후 발견된 버그

1. 플랫폼 변경이 안된채로 빌드되는 경우

=> 플랫폼 변경하는 코드를 삽입하여도 실질적으로 코드와 연결된 라이브러리들이 변경전 라이브러리들을 보고 있어서 컴파일 에러 발생

해결 : 빌드 과정을 하나 상단에 추가하여 플랫폼 변경을 미리 해준 후, 다음 빌드 과정에서 빌드를 해준다.

2. Definition이 변경 안되는 경우

=> preprocess defininition들을 위의 소스와 같이 변경을 해도 적용이 안되는 버그 발생

해결 : 1번과 같이 prebuild를 만들어 그 안에서 빌드전에 세팅해야할 것들을 미리 세팅한다.