---
description: 도커 컨테이너에 대한 짧은 이야기
---

# 컨테이너 사전 지식

### 도커의 필요성

과거에는 물리적인 서버에서 운영체제 버전에 맞는 소프트웨어를 설치하여 운영 하는 환경이었다. 이러한 구성을 스노우플레이크 서버 라고 불렀는데, 이렇게 구성된 서버는 다시 재현해내기 어렵다.&#x20;

{% hint style="info" %}
_**스노우 플레이크 서버**_

> _**한 번 설정을 하고 다시 설정이 불가능한, "마치 눈 처럼 녹아버리는" 서버의 형태**_

운영 환경에서 소프트웨어 업데이트와 설치를 반복 하며 눈이 하나씩 쌓이는 것 처럼 운영 중인 환경이 눈 처럼 쌓인다는 것 때문이다.

[참고 블로그](https://bcho.tistory.com/1224)
{% endhint %}



### 도커 컨테이너는 프로세스를 관리한다

> _**컨테이너는 Host OS의 시스템 커널을 사용한다.**_

<figure><img src="https://1313571729-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FgBw67Z5q88Wr3aVTqOqn%2Fuploads%2Fukeg63HZifaKqc0s4qIb%2Fimage.png?alt=media&#x26;token=cfb80ecf-ea82-42ab-bf4f-ed79f6243427" alt=""><figcaption></figcaption></figure>



#### 가상머신과 컨테이너의 차이

***

<figure><img src="https://1313571729-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FgBw67Z5q88Wr3aVTqOqn%2Fuploads%2Fu4A0WxAwErtwa3ea8qS4%2Fimage.png?alt=media&#x26;token=08056f70-df33-4b65-84aa-8417262f2ab7" alt=""><figcaption></figcaption></figure>

> **가상머신**
>
> 기존의 가상화 기술은 하이퍼바이저를 이용해 여러 개의 운영체제를 하나의 호스트에서 생성해 사용하는 방식

{% hint style="info" %}
**하이퍼 바이저**

호스트 컴퓨터에서 다수의 운영체제를 동시에 실행하기 위한 논리적 플랫폼
{% endhint %}

하이퍼 바이저가 존재하고 각각의 Guest OS 들이 리소스들을 활용하기 위해서 하이퍼 바이저를 통해 시스템 콜을 발생 시킨다.

이 때, 중요한 점은 <mark style="color:red;">**각종 시스템 자원을 가상화하고 독립된 공간을 생성하는 작업은 반드시 하이퍼바이저를 거치기 때문에 일반 Host에 비해 성능의 손실이 발생**</mark>한다는 것이다.

이외에도 가상머신은 <mark style="color:red;">**GuestOS를 사용하기 위한 라이브러리, 커널 등을 전부 포함하기에 가상 머신을 배포하기 위한 이미지로 만들었을 때 이미지 크기가 커져**</mark> 가상머신 이미지를 애플리케이션으로 배포하기는 부담스럽다.

> **도커 컨테이너**
>
> 가상 머신과 달리 도커 컨테이너는 도커 컨테이너 엔진이 별도의 프로세스로 할당 되어 실행이 되고 각각의 프로세스들을 격리해서 사용한다.

* <mark style="color:purple;">**chroot**</mark>로 특정 자원만 사용하도록 제한
* <mark style="color:purple;">**cgroup**</mark>을 사용하여 자원의 사용량을 제한
* <mark style="color:purple;">**namespace**</mark>로 특정 유저만 자원을 볼 수 있도록 제한
* <mark style="color:purple;">**overlay network**</mark> 등 네트워크 가상화 기술 활용
* <mark style="color:purple;">**union file system**</mark> (AUFS, overlay2)로 이식성, 비용절감

컨테이너에 필요한 커널은 호스트의 커널을 공유해 사용하고 컨테이너 안에는 애플리케이션을 구동하는데 필요한 라이브러리 및 실행 파일만 존재하기 때문에 컨테이너를 이미지로 만들었을 때 이미지의 용량 또한 가상 머신에 비해 대폭 줄어든다.

<mark style="color:red;">**무엇보다 컨테이너의 내용을 수정해도 호스트 OS에 영향을 끼치지 않고 애플리케이션의 개발과 배포가 편해지며 여러 애플리케이션의 독립성과 확장성이 높아진다.**</mark>\


> 도커 데몬

사용자가 명령어를 입력하면 **`docker.sock`**&#xC744; 통해 도커 데몬의 API 를 호출한다. 도커 데몬에서 발생한 이벤트는 docker events 를 통해 확인할 수 있다.



**"docker 명령어가 dockerd라는 도커 데몬과 docker.sock을 참조하고 있음을 확인할 수 있다"**

{% code overflow="wrap" %}
```
sudo lsof -c docker
dockerd     880 root  txt       REG              202,1 102066512      55737 /usr/bin/dockerd
dockerd     880 root    6u     unix 0xffff953085804400       0t0      18324 /var/run/docker.sock type=STREAM
```
{% endcode %}

<mark style="color:purple;">**docker stat**</mark> 과 <mark style="color:purple;">**docker system df**</mark> 를 통해 도칵 현재 Host의 자원을 얼마나 사용중인지 확인할 수 있다.

> "_**도커 데몬은 크게, 컨테이너 이미지 빌드, 관리, 공유, 실행 및 컨테이너 인스턴스 관리 등의 기능을 하며, 앞에서 살펴봤듯 모든 컨테이너를 자식 프로세스로 하는데 이에 따른 문제점은 없을까?**_"

:point\_right: 도커의 문제점은 해당 [아티클](https://www.samsungsds.com/kr/insights/docker.html)에서 읽어볼 수 있습니다.



> **"하지만 파일 시스템은 다르다"**

<figure><img src="https://1313571729-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FgBw67Z5q88Wr3aVTqOqn%2Fuploads%2F5lZTKM4LvZsZnapCj41M%2Fimage.png?alt=media&#x26;token=de1fad9e-da39-4530-8fd7-d6c984994c3d" alt=""><figcaption></figcaption></figure>

프로세스를 별개로 실행 시켜 파일 시스템이 다르다는 것을 어떻게 할 수 있는 것일까?

<figure><img src="https://1313571729-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FgBw67Z5q88Wr3aVTqOqn%2Fuploads%2FofuWF3FKl1Sqk6qW38Nc%2Fimage.png?alt=media&#x26;token=d84229ff-56af-4118-9351-0505e5b44e96" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
_**chroot**_

루트 파일 시스템을 강제로 인식시켜 프로세스를 실행하는 것
{% endhint %}

프로세스는 이와 같이 자기 자신이 위치해 있는 곳을 루트로 인식 하기 때문에 컨테이너가 곧 자신의 세상이라고 인식하는 것이다.

> **그래서 컨테이너 내에선 자기가 첫번째 프로세스로 할당된다.**

<figure><img src="https://1313571729-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FgBw67Z5q88Wr3aVTqOqn%2Fuploads%2FD21J4I3JpICaE9eMF6zj%2Fimage.png?alt=media&#x26;token=e39783c3-6b44-4ac2-a794-aa7217d9f0e3" alt=""><figcaption></figcaption></figure>

> _**"Host OS는 컨테이너를 어떻게 바라볼까?"**_

위 프로세스는 Nginx 컨테이너인데, Nginx를 직접 실행하나, 컨테이너 안에서 실행되나 Host OS에겐 그저 동일한 Nginx 프로세스일 뿐이다.



### 도커 컨테이너 네트워크

#### 격리중인 컨테이너가 다른 프로세스와 통신하기

***

> _**"도커 컨테이너는 격리 되어 있는데 어떻게 다른 컨테이너와 통신이 가능할까?"**_

도커 컨테이너는 컨테이너들의 위치를 아는 요소가 존재한다. 만약, Nginx 프로세스를 관리하는 도커 컨테이너 1개를 생성 했을 때 망 구성이 어떻게 될지 살펴보자.

{% stepper %}
{% step %}
### 네트워크 인터페이스 카드 생성

<figure><img src="https://1313571729-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FgBw67Z5q88Wr3aVTqOqn%2Fuploads%2FIj3gyASThh2HLhN6mBfw%2Fimage.png?alt=media&#x26;token=ddd16295-c061-4748-b03f-976fb1da7fb9" alt=""><figcaption></figcaption></figure>

위에서 <mark style="color:purple;">**chroot**</mark>를 통해 독자적인 파일 시스템이 생성 된다고 했었는데, 이 때 <mark style="color:red;">**네트워크 인터페이스 카드**</mark>도 하나 생성된다.

컨테이너 내부 네트워크 상태를 확인 해보면 컨테이너에 "<mark style="color:red;">**MAC 주소와 IP가 할당 되어있음**</mark>"
{% endstep %}

{% step %}
### 라우팅 테이블의 게이트웨이 설정

<figure><img src="https://1313571729-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FgBw67Z5q88Wr3aVTqOqn%2Fuploads%2FXAt3LDV3YySMKXXpYe5I%2Fimage.png?alt=media&#x26;token=469ca591-afcf-4c8d-a1ab-c2231e6d8fc5" alt=""><figcaption></figcaption></figure>

컨테이너 내부 라우팅 테이블 구성을 보면 **모르는 대역의 경우 `172.17.0.1`로 보내도록 게이트웨이가 설정** 되어있음
{% endstep %}

{% step %}
### L2 스위치 장비 소프트웨어 버전인 Bridge driver

<figure><img src="https://1313571729-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FgBw67Z5q88Wr3aVTqOqn%2Fuploads%2FtOZ3kHlZ4xakzIP8uwtS%2Fimage.png?alt=media&#x26;token=7e328bf1-0474-44e6-8746-a8973a0d2578" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
**Bridge**

* L2 Switch 장비의 소프트웨어 버전
{% endhint %}

**`172.17.0.1`이 할당된 게이트웨이가 존재하고 이 네트워크는 bridge 모드로 동작게된다.**
{% endstep %}

{% step %}
### docker0

<figure><img src="https://1313571729-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FgBw67Z5q88Wr3aVTqOqn%2Fuploads%2F3yNihZkTbVlUG71SnVMR%2Fimage.png?alt=media&#x26;token=f157fa3b-4d41-4d06-9bc0-904f66fb81b8" alt=""><figcaption></figcaption></figure>

**`172.17.0.1`은&#x20;**<mark style="color:red;">**Host OS의 docker0 라는 녀석에게 할당**</mark>되어 있다.
{% endstep %}
{% endstepper %}

> "요약 해보자면"

<figure><img src="https://1313571729-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FgBw67Z5q88Wr3aVTqOqn%2Fuploads%2F02PI8fioMMsPvSpxxlAH%2Fimage.png?alt=media&#x26;token=265d7349-5e0b-44ef-880c-f27e776b5af0" alt=""><figcaption></figcaption></figure>

> **망 외부 통신**

IP가 `172.17.0.3`인 녀석이 `172.17.0.0/16` <mark style="color:green;">**망 외부**</mark>와 통신하려고 할 때 `172.17.0.1`에게 통신을 보낸다.

물론, 이 때 서버 내에서 `172.17.0,3` IP로 직접 통신도 가능하다.



> **망 내부 통신**

IP가 `172.17.0.3`인 녀석이 `172.17.0.0/16` <mark style="color:green;">**망 내부**</mark>와 통신하려고 할 때 docker0는 L2 Switch 역할을 하는 bridge 모드를 통해서 직접 통신을 한다.

{% hint style="info" %}
**주소를 알고 있다면 다른 컨테이너에게 직접 접근 하고 모른다면 전체 컨테이너에게 broadcast를 보낸다.**
{% endhint %}

{% hint style="info" %}
Summary

1. 컨테이너를 생성하면, 가상 네트워크 인터페이스가 생성이 되고 컨테이너 내의 <mark style="color:green;">**eth0**</mark>과 연결된다.
2. 컨테이너들의 가상 네트워크 인터페이스들은 <mark style="color:green;">**docker0**</mark>을 통해 컨테이너들간에 통신이 가능하다.
3. 컨테이너는 gateway인 <mark style="color:green;">**docker0**</mark>을 거쳐 외부와 통신이 가능하다.
{% endhint %}



> **"컨테이너 IP를 직접 다 확인하기 어렵고 외부로부터 오는 요청은 Host의 NIC로 오는데 어떻게 다른 컨테이너간 통신을 유연하게 이룰 수 있지?"**

<figure><img src="https://1313571729-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FgBw67Z5q88Wr3aVTqOqn%2Fuploads%2FoqcFjc6SVOL2bAcpa4BK%2Fimage.png?alt=media&#x26;token=c710a6a7-42a4-456c-bc38-6c332e07102a" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
_**포트 포워딩 활용**_

<mark style="color:green;">**Host OS의 80 포트번호를 사용하는 소켓 파일**</mark>과 <mark style="color:blue;">**컨테이너 내의 80 포트번호를 사용하는 소켓 파일**</mark>을 서로 연결할 수 있다.

포트 포워딩을 활용하면 Host OS가 직접 요청을 처리할 수 있게 된다.
{% endhint %}

<figure><img src="https://1313571729-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FgBw67Z5q88Wr3aVTqOqn%2Fuploads%2FEKPK8QcR27MzWF1xnwKf%2Fimage.png?alt=media&#x26;token=1135ea1e-0012-49bc-8b76-15b685a1fc35" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
방화벽 정책(iptables)

도커 컨테이너 명령어로 포트포워딩을 활용 하면 방화벽 정책에 목적지가 추가된 것을 확인할 수 있다.
{% endhint %}



#### 도커 컨테이너의 영속성

***

> "컨테이너 이미지를 제거하면 어떻게 될까?"

<figure><img src="https://1313571729-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FgBw67Z5q88Wr3aVTqOqn%2Fuploads%2FbvcISKOuiINyznvL1ZeH%2Fimage.png?alt=media&#x26;token=4fe7dc2c-fa24-4ce8-bebb-47862a67b84d" alt=""><figcaption></figcaption></figure>

컨테이너 이미지는 <mark style="color:green;">**읽기 전용**</mark>이다. 즉, 컨테이너를 제거하면 메모리에서 사라지기 때문에 영속적인 데이터를 다룰 수 없다.

그렇기 때문에 영속성 데이터는 위 이미지 처럼 컨테이너가 아닌 외부에 데이터를 저장하고, 컨테이너는 그 데이터로 동작할 수 있도록 stateless 하게 구성해야한다.

