전자책 출간 알림 [마이크로서비스패턴 쉽게 개발하기]

티스토리 뷰

학습 목표

마이크로서비스는 ‘Time to market을 달성하는 ‘Speedy’한 서비스, 수익 창출하는 ‘Service Always’한 서비스, 비용 최적화하는 ‘Save Cost’한 서비스를 보장해 기업의 성공 확률을 높여주는 강력한 수단입니다. 이러한 ‘3S’를 보장하는 서비스는 마이크로화, 독립적 빌드/배포/스케일, 반복/순환적인 발전이 되어야 합니다. 그러려면 마이크로서비스 간의 독립성을 유지해야 하는데, 독립성 유지는 컨테이너로 격리하는 방법이 가장 좋습니다. 그래서 마이크로서비스에 컨테이너 기술이 많이 이용되고 있습니다.  

마이크로서비스의 컨테이너화에 가장 많이 사용하는 제품 중 도커Docker를 알아보겠습니다. 

이 장의 목표는 도커 그 자체에 대한 깊이 있는 기술 지식을 전달하는것이  아니라 마이크로서비스를 컨테이너화 하기 위해 필요한 실용적인 핵심 지식만을 전달하는 것입니다. 

컨테이너 기술이 필요한 근본적인 이유를 먼저 이해할 것을 권장합니다.

그 근본적인 이유인 일하는 방식 변화에 대해 아래 글을 먼저 읽어 보시기 바랍니다.

https://happycloud-lee.tistory.com/261?category=832246 

 

일하는 방식 변화 핵심만 빠르게 이해하기: 애자일, 마이크로서비스, 데브옵스, 클라우드

새로운 변화의 물결 학습 목표 마이크로서비스가 최근에 왜 주목 받고 있는지 거시적 관점인 일하는 방식 변화의 측면에서 이해하는 것이 목표입니다. 이를 위해 일하는 방식 변화가 왜 필요한

happycloud-lee.tistory.com

 

도커 소개

도커는 어플리케이션 구동에 필요한 모든 것들을 묶어서 서로 격리된 컨테이너로 실행해주는 제품입니다. 

여러분들이 만든 어플리케이션 소스, 필요한 라이브러리, 실행에 필요한 런타임 엔진, OS를 압축파일처럼 하나의 이미지로 만들어 줍니다. 이 이미지를 배포할 대상 머신(물리머신 또는 가상머신)에 가상 프로세스를 만들어 실행해 줍니다. 이 가상 프로세스는 서로 격리되어 있고 자체적으로 완벽한 하나의 작은 서버로서 동작 합니다. 

기존 가상화 기술이 하드웨어나 OS를 가상화 한다면 도커는 프로세스를 가상화 한다는 큰 차이가 있습니다. 

그래서 도커를 ‘프로세스 가상화 도구'의 하나라고 정의할 수도 있습니다. 

도커는 2013년 프로세스 가상화 도구 중 가장 처음 시장에 나왔고 현재까지도 가장 널리 사용되는 제품입니다.  

 

도커 장단점

우선 컨테이너의 가장 큰 장점은 서비스 상호간의 독립성을 보장하여 서비스의 독립적 빌드/배포/스케일이 가능해 진다는 것입니다. 

반면 컨테이너의 가장 큰 단점은 프로세스 가상화로 인해 베어메탈(가상화 되지 않은 물리적인 머신) 보다는 약간 느려진다는 것입니다.  하지만 이 단점은 실제 서비스를 제공하는데는 무시해도 될 정도라 큰 문제는 없습니다. 

 

도커와 같은 프로세스 가상화 제품 중 최근 가장 주목 받고 있는 것이 크라이오CRI-O입니다. 

2013년 나온 초기 도커의 장점은 클라이언트인 도커Docker CLI(Command Line Interface)와 서버인 도커 데몬Docker Daemon으로 구성된 단순한 클라이언트-서버 구조라는 것입니다. 도커 데몬Docker Daemon이 이미지의 제작 및 관리와 컨테이너 실행 및 관리를 모두 해주는 모놀리식 구조라 사용하기 쉽고 편리했습니다.  

하지만 쉽고 편리하다는 장점은 시간이 지나면서 단점으로 변했습니다. 

도커 데몬에 문제가 생기면 전체 컨테이너에 장애가 발생하는 것이 가장 큰 문제점으로 대두 되었습니다. 

두번째 문제는 컨테이너를 실행한 유저 이름이 OS를 접근한 유저 이름과 달라진다는 것입니다. 유저 이름이 변경되기 때문에 누가 컨테이너를 실행 했는지 추적하지 못한다는 보안상의 문제가 생깁니다.    

 

그래서 도커사는 도커 데몬에서 컨테이너를 실행하고 관리하는 기능을 ‘contaierd’로 분리하였습니다. 

크라이오CRI-O는 초기 도커의 문제점을 해결하기 위해 Red Hat, Intel, SUSE, Hyper, IBM이 만든 쿠버네티스 전용의 컨테이너 런타임 제품입니다. 

containerd와 CRI-O는 모놀리식 구조로 인한 도커의 단일 실패점(Single Point of Failure) 문제를 해결하기 위해 나온 컨테이너 런타임 제품이며 앞으로 이 두 제품이 컨테이너 시장을 선도해 갈 것이라고 많은 전문가들이 얘기하고 있습니다. 

그럼에도 불구하고 도커를 배워야 하는 이유는 여전히 있습니다. 

첫째, 아직까지도 가장 많이 사용하는 기술이라 실무에 필요하기 때문입니다. 

둘째, ‘containerd’는 도커에 내장된 컨테이너 런타임 엔진이라 여전히 도커는 컨테이너의 중요 제품이기 때문입니다. 

 

쿠버네티스 편에서는 컨테이너 런타임 툴로 CRI-O를 사용하므로 여러분은 최신 컨테이너 기술을 모두 체험해 보실 수 있습니다. 


1. 도커 설치하기

도커를 실습하려면 가상 머신이 필요합니다. 퍼블릭 클라우드에서 구매하거나, 여러분의 PC에 직접 설치해도 됩니다. 맥OS와 윈도우에 설치해도 되지만 컨테이너 기술이 리눅스에 최적화되어 있으니 가능하면 리눅스에 설치하기 바랍니다. 이 책에서는 오라클 버추얼박스Oracle Virtualbox를 이용해 가상 머신을 직접 만들어 실습하겠습니다. 

 

1.1 가상 머신 준비하기

가상 머신은 센트OSCentOS 버전 8로 구성 하겠습니다.  

 

아래 글에 자세한 가이드가 나와 있으니 그대로 따라 하면 됩니다. 

https://happycloud-lee.tistory.com/233

 

가상머신 구성 순서는 아래와 같습니다. 

01. 오라클 버추얼박스 프로그램 설치: https://www.virtualbox.org/wiki/Downloads

* 맥 사용자는 ‘OS X hosts’를 선택하면 됩니다. 

02. CentOS 이미지 다운로드: https://osboxes.org 

VM IMAGES > VirtualBox Images 선택 후 CentOS 클릭 ⇒ ‘Download VirtualBox (VDI) image 클릭 ⇒ CentOS 8.4 Server 다운로드  

03. VM 만들기: 메모리는 2048MB, CPU는 2개로 설정

04. VM 네트워크 설정 

05. SSH 서버 설치 및 터미널 연결

<팁>

로컬 가상머신은 한참 사용을 안하면 인터넷이 끊기는 경우가 있습니다. 

가상머신을 아래와 같이 재부팅하면 됩니다. 

</>



1.2 도커 설치

아래와 같은 순서로 ‘docker-ce-20.10.11-3.el8’ 버전을 설치해 주십시오.   

 

01. VM 로그인: 구성한 VM을 로그인 합니다. 암호는 위 구성 작업 시 변경한 root암호를 입력하면 됩니다. 

❯ ssh root@192.168.56.2
root@192.168.56.2's password:
Web console: https://osboxes:9090/ or https://10.0.2.3:9090/

Last login: Fri Dec 31 18:47:28 2021 from 192.168.56.1
[root@osboxes ~]#

 

02. yum-utils 설치

[root@osboxes ~]# dnf install -y yum-utils



03. 리포지토리 추가

[root@osboxes ~]# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

 

04. 도커 설치

설치할 수 있는 도커 버전을 확인 합니다. 

[root@osboxes ~]# dnf list docker-ce
Failed to set locale, defaulting to C.UTF-8
Docker CE Stable - x86_64                  617  B/s | 3.5 kB     00:05
Available Packages
docker-ce.x86_64             3:20.10.12-3.el8              docker-ce-stable

 

버전 ‘3:20.10.12'을 설치 합니다. 

[root@osboxes ~]# dnf install -y docker-ce-3:20.10.12 docker-ce-cli containerd.io

 

설치가 잘 되었는지 확인하기 위해 도커 버전을 확인 합니다. 

아래와 같이 Client인 Docker CLI, Server인 Docker Engine, containerd, runc, docker-init가 설치된것을 확인할 수 있습니다. 

Docker Engine은 Docker CLI로 부터의 요청을 받아 containerd로 전달하는 역할을 합니다. 

containerd는 컨테이너 이미지 압축을 해제하고 runc에 컨테이너 실행을 요청 합니다. 또한 컨테이너를 관리하는 역할도 합니다.   

[root@osboxes ~]# docker version
Client: Docker Engine - Community
Version:           20.10.12
...

Server: Docker Engine - Community
Engine:
  Version:          20.10.12
  ...
containerd:
  Version:          1.4.12
  ...
runc:
  Version:          1.0.2
  ...
docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

 

 

2. 도커 아키텍처

1장에서 예를 든 컨테이너 하우스 예제를 갖고 도커 아키텍처를 설명하고 간단한 예제를 실습함으로써 도커 아키텍처를 쉽게 이해해 보겠습니다. 

 

2.1 도커 아키텍처 이해

 

컨테이너 하우스를 구매해서 여행하려면 어떤 작업들을 해야 할까요 ?

아래와 같은 순서로 컨테이너하우스를 구매하고 어행을 떠날 것입니다. 

 

❶ 컨테이너하우스 구매 사이트를 들어가신 후 맘에 드는 컨테이너 하우스를 주문하면 ➝ ❷ 주문 요청은 주문처리 엔진에 의해서 처리됩니다. ❸ 주문 접수 후 컨테이너하우스 창고에서 여러분이 주문한 컨테이너하우스 모델을 찾아 ➍ 집 앞마당으로 배달합니다. ➎ 그러면 여러분은 컨테이너 하우스를 트럭에 싣고 여행을 떠나면 됩니다. 

이 모습을 도커 아키텍처와 매치하면 다음과 같습니다.

 

여기서 컨테이너 주문은 도커에서 CLI 또는 API를 이용해 Docker Daemon에 요청하는 행위입니다. 컨테이너하우스 창고는 컨테이너 이미지 레지스트리Image Registry와 동일하게 컨테이너 하우스들을 모아 놓은 사이트입니다.  창고에 많은 컨테이너 하우스가 있는 것처럼 이미지 레지스트리에는 이미지들이 저장되어 있습니다. 각 컨테이너하우스가 모델별로 있는 것처럼 각 컨테이너 이미지들도 버전별로 보관되어 있습니다. 여러 버전을 모아 놓은 이미지를 리포지토리Repository라고 부릅니다. 

주문한 컨테이너 하우스가 집 앞마당으로 배달되는 것처럼 요청된 이미지는 실행될 가상 머신이나 물리 머신에 내려 받아집니다. 

그리고 내려받은 이미지가 실행되면 비로소 컨테이너Container라는 작은 서버가 되어 서비스를 제공합니다. 

 

순수하게 도커 아키텍처를 그림으로 표현하면 다음과 같습니다. 

 

Docker Daemon이 containerd를 이용하여 컨테이너를 실행하는 절차는 아래와 같습니다. 

 

여러분이 도커를 설치한 가상 머신에는 클라이언트인 Docker CLI와 서버인 Docker Daemon이 물리적으로 한 곳에 있습니다. 

혹시 ‘PC에는 Docker CLI만 있고 PC에서 원격의 Docker Daemon을 연결해 명령들을 수행하게 할 수 없을까?’라는 생각을 한 분이 있나요 ? 

좋은 질문입니다. 답은 ‘가능 하다. 하지만 개발자가 거기까지 알 필요는 없다’입니다. 왜냐하면 실무에서는 컨테이너관리 플랫폼인 쿠버네티스를 사용할 거고 쿠버네티스에서 더 편한 방법으로 이러한 클라이언트-서버 구조를 지원하기 때문입니다. 

 

2.2 도커 아키텍처 실습

그럼 이제까지 설명한 아키텍처가 정말 동작하는지 간단히 테스트 해보겠습니다. 

테스트를 위한 컨테이너 이미지는 ‘ghost’라는 간단한 블로그 서비스입니다. 

먼저 이미지를 레지스트리로부터 내려받습니다. 

# docker pull ghost:4

 

위 명령을 실행 하면 아래 그림과 같이 어딘가에서 ‘ghost’라는 이미지를 내려받기 할 겁니다. 

[root@osboxes ~]# docker pull ghost:4
Using default tag: latest
latest: Pulling from library/ghost
72a69066d2fe: Already exists
24892ef5fd86: Already exists
b352cb85f08b: Already exists
d184ccfb32cc: Already exists
a1aa0950435d: Already exists
6af76194e172: Already exists
6ecb9543cf3f: Already exists
f5dc5f4a448f: Pull complete
c5c6987f6d28: Pull complete
Digest: sha256:8123a7eabc533746cfa7302c71b780e763ee292f92c10f44e12d08baa0b489f9
Status: Downloaded newer image for ghost:latest
docker.io/library/ghost:latest

 

맨 마지막 줄에 나오는 ‘docker.io/library/ghost:latest’에 이미지를 어디에서 내려받기 했는지 정보가 나옵니다. 

맨 앞에 나오는 docker.io가 이미지 레지스트리의 주소입니다. docker.io가 어디 있는 레지스트리인지는 조금 후 컨테이너 이미지를 만드는 과정에서 설명하겠습니다. 

 

이제 가상 머신에 내려받은 이미지를 실행해 컨테이너로 실행 합니다. 

# docker run -d --name myghost -p 8000:2368 -e url=http://192.168.56.2:8000 ghost:4
※ 옵션 설명
1) -- name: 컨테이너 이름
2) -p <외부접근 포트>:<컨테이너 내부 포트> : 외부접근 포트로 request가 들어오면 컨테이너 내부 포트로 port 연결을 함
3) -e <환경 설정key>=<환경 설정 value> : 컨테이너 내부에서 사용할 환경 설정 값을 지정함.
※ url의 ip는 본인 가상 머신의 ip를 입력하십시오.
※ -p뒤 외부 포트번호(위 예에서는 8000번)와 url의 포트번호는 마음대로 지정하십시오

TIP) 실행 시 아래와 같은 에러가 발생하면 iptables에 문제가 있는 것이니 그 아래에 있는 명령들로 Fix한 후 다시 수행하십시오. 

/usr/bin/docker-current: Error response from daemon: driver failed programming external connectivity on endpoint tender_mestorf (62c1781b3d1f93ab06131f9a88bc038d6dbf8c677c5aca8830c8481ec5a06821):  (iptables failed: iptables --wait -t nat -A DOCKER -p tcp -d 0/0 --dport 8080 -j DNAT --to-destination 172.17.0.2:8080 ! -i docker0: iptables: No chain/target/match by that name.
 (exit status 1)).
$ iptables -t filter -F  
$ iptables -t filter -X 
$ systemctl restart docker

 

실행이 되면 다음과 같이 무슨 난수같은 것이 결과로 나옵니다. 

컨테이너의 프로세스 번호입니다. 

[root@osboxes ~]# docker run -d --name myghost -p 8000:2368 -e url=http://192.168.56.2:8000 ghost:4
e22ca46a52dcb33a66cc9e4756e04e84f26d82a14803e44b8c8937210c5ab7a8



일단 여러분 PC에서 웹브라우저를 열고 환경 변수 ‘url’에 지정한 주소로 접근해보십시오.

아래 그림과 같이 멋진 페이지가 열릴 겁니다. 

 

 

어떠십니까 ? 도커 아키텍처대로 이미지를 내려받기 해 컨테이너로 실행하는 것이 그렇게 어렵지 않죠 ? 

이제 여러분은 필요한 이미지의 전체 경로(위 예에서 docker.io/library/ghost:latest)만 알면 그 이미지를 컨테이너로 실행해 빠르게 서비스를 제공할 수 있습니다. 

 

남이 만들어 놓은 이미지를 이용해 컨테이너로 실행시키는 경우도 많지만 직접 마이크로서비스를 개발해 컨테이너 이미지로 만들고 컨테이너로 실행하는 경우도 많습니다. 

이제 그 방법을 실습 하면서 주요 도커 명령들을 이해해보겠습니다. 

 

전체적인 절차는 다음과 같습니다. 

‘2.2 컨테이너 이미지 만들기’는 ❶ 번에서 ❹ 번까지, ‘2.3 컨테이너 이미지 푸시’는 ❺ 번에서 ❻ 번까지, ‘2.4 컨테이너 실행 및 관리’는 ❼ 번 이후의 작업을 실습합니다.  





3. 컨테이너 이미지 만들기

 

PC에서 간단한 어플리케이션을 개발한 후 아래와 같은 순서로 컨테이너 이미지를 만듭니다. 

❶ 소스 업로드: 개발한 어플리케이션 소스를 깃Git 레지스트리Registry에 올림

❷ 소스 다운로드: 컨테이너 이미지를 만들 작업 VM에 어플리케이션 소스를 내려 받음

❸ 컨테이너 이미지 만들기: 컨테이너 이미지 제작 

❹ 이미지 생성 확인: 컨테이너 이미지가 정상적으로 생성 되었는지 확인



3.1 프로그램 설치 및 어플리케이션 개발

어플리케이션 개발을 위해 PC에 아래 프로그램들을 먼저 설치 합니다.

※ MS Visual Studio Code(이하 vscode) 설치 후에 ‘code’ 유틸리티도 설치 하십시오. 

 

실습을 위해 Node.js로 간단한 웹 서버 어플리케이션을 만들겠습니다. 

 

PC에 작업할 디렉토리를 만듭니다. 

PC의 {사용자 홈 디렉토리}밑에 ‘docker/hello’ 디렉토리를 만들고 이동 합니다. 

- 리눅스/맥OS: mkdir -p ~/docker/hello && cd ~/docker/hello

- 윈도우: 윈도우 탐색기에서에서 디렉토리를 만들고, DOS창에서 그 디렉토리를 엽니다. 

사용자 홈 디렉토리는 리눅스/맥OS에서는 ‘~’기호를 사용하면 되고 윈도우는 ‘C:\사용자\{로그인 username}’입니다.

 

작업 디렉토리에서 ‘code . ‘이라고 입력 합니다. vscode가 실행될 겁니다. 

vscode의 메인 메뉴에서 ‘터미널 > 새 터미널’을 클릭 합니다. 

이제 개발을 위한 준비가 끝났습니다. 

새로운 Node.js 어플리케이션을 만들기 위해서는 어플리케이션 정의 파일을 만들어야 합니다. 

터미널에서 ‘npm init’을 실행하고 계속 엔터를 칩니다. 

어플리케이션 정의 파일인 ‘package.json’이 생성될 겁니다. 

package.json파일을 열어보면 다음과 같을 겁니다. “main”항목에 정의된 index.js가 시작 프로그램입니다. 

{
  "name": "hello",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

 

파일 탐색기에서 ‘HELLO’를 클릭하고 우측의 ‘새 파일’아이콘을 눌러 index.js파일을 생성 합니다. 

 

index.js의 내용을 다음과 같이 작성 합니다. 

var http = require('http');
var os = require('os');
var port = 8080;

http.createServer( (req, res) => {
  console.log('Requested: ' + req.url);
  res.writeHead(200, { 'Content-Type':'text/html; charset=utf-8' });
  res.write('<h1>docker 예제1</h1>');
  res.write('<h2>Hello node!</h2>');
  res.end();
}).listen(port, () => {
  console.log('Listen ... ' + os.hostname()+':'+port);
});



터미널에서 ‘node index’라고 입력해 어플리케이션을 실행 합니다.

~/docker/hello main*
❯ node index
Listen ... 192.168.nate.com:8080



웹브라우저를 열고 http://localhost:8080으로 접근 합니다.

간단한 웹 서버 어플리케이션을 개발했습니다. 

메시지를 바꾸고 싶으면 소스를 고친 후 터미널에서 ‘CTRL-C’로 중단하고 다시 ‘node index’로 서버를 시작하면 됩니다. 

 

3.2 소스 업로드

이제 개발된 웹 서버 어플리케이션을 소스 형상관리 툴인 깃Git에 업로드 합니다. 

깃은 깃허브GitHub를 사용하겠습니다. 

https://github.com을 웹브라우저에서 열고 아직 회원가입을 안했으면 하십시오. 

회원 가입 후 ‘Repositories’탭을 누르고 [New]버튼을 눌러 새로운 리포지토리Repository를 만듭니다. 

 

가입한 사용자명 하위에 ‘hello’라는 리포지토리를 만듭니다. 다른 옵션은 기본 그대로 놔둡니다.

 

Git 기초 지식

깃을 처음 접한 분들을 위해 깃에 대한 기초 지식을 알려 드립니다. 

깃 저장소는 로컬 깃 리포지토리Local Git Repository와 원격 깃 리포지토리Remote Git Repository로 구성되어 있습니다. 

방금 github.com에 만든 리포지토리는 원격 깃 리포지토리입니다. 

여러분이 만든 PC의 소스를 이 원격 깃 리포지토리에 올리려면 PC에 로컬 깃 리포지토리를 만들어야 합니다. 

로컬 깃 리포지토리는 다음과 같이 3개의 영역으로 구성됩니다. 

‘working directory’는 소스가 있는 디렉토리를 말합니다. ‘index’는 업로드 후보 파일 정보가 관리되는 영역이고 ‘HEAD’는 최종 확정된 업로드 파일 정보가 관리되는 영역입니다. 

출처: Roger Dudler의 Git 간편 안내서

 

그래서 소스를 업로드 하려면 다음과 같은 단계를 거쳐야 합니다. 

- 로컬 깃 리포지토리 생성: git init

- 로컬 깃 리포지토리에 원격 깃 리포지토리 연결: git remote add origin {원격 리포지토리 주소}

- working directory에서 변경된 파일들을 index영역에 ‘add’: git add {파일}

- HEAD영역에 ‘commit’: git commit -m ‘{commit 메시지}’

- 로컬 깃 리포지토리 브랜치branch 변경: git branch -M {branch}

- 업로드: git push origin {branch} 

※ 리포지토리는 소스를 브랜치branch별로 관리 합니다. 예를 들어 ‘dev’, ‘stage’, ‘prod’와 같이 배포 대상 환경에 따라 나누어 관리할 수 있습니다. GitHub의 기본 브랜치는 ‘main’입니다. 

깃 푸시전에 로컬 깃 리포지토리의 브랜치를 푸시할 원격 깃 리포지토리의 브랜치와 동일하게 바꿔줘야 합니다. 

 

Git에 대한 더 자세한 정보는 아래 글을 참고 하십시오.

https://happycloud-lee.tistory.com/93

 

 

vscode의 터미널로 돌아와 아래 명령을 입력해 로컬 깃 리포지토리를 만듭니다. 

git init

이 PC에서 최초로 git을 사용하는 경우는 아래 작업을 해줘야 합니다. username과 email을 셋팅하는 겁니다.

# {ondal}과 {ondal@gmail.com}은 본인에 맞게 수정하세요

> git config user.name ondal --global
> git config user.email ondal@gmail.com --global

 

터미널에서 로컬 깃 리포지토리에 원격 깃 리포지토리를 연결 합니다. 아래 노란색 볼드체 부분은 여러분의 GitHub 사용자명으로 바꾸셔야 합니다. 

이 명령의 의미는 별명이 ‘origin’이고 실제 주소는 ‘https://github.com/hiondal/hello.git’인 원격 깃 리포지토리를 로컬 깃 리포지토리에 추가한다는 겁니다. 

git remote add origin https://github.com/hiondal/hello.git

 

변경된 파일을 index영역에 ‘add’하고 HEAD영역에 ‘commit’합니다. ‘git add’뒤에는 업로드할 파일명을 입력해야 하는데 ‘.’이라고 입력하면 현재 작업 디렉토리 서브의 모든 파일 중 변경된 파일만 자동으로 추가해줍니다. 

git add .
git commit -m "first push"

 

로컬 깃 리포지토리의 브랜치를 원격 깃 리포지토리와 동일하게 ‘main’으로 바꿉니다. 

‘-M’옵션은 브랜치가 없으면 생성하고 이미 그 브랜치가 있어도 에러 없이 변경하는 옵션입니다.  

git branch -M main

 

마지막으로 원격 깃 리포지토리로 푸시 합니다. 아래 명령의 의미는 ‘origin’이라는 별명을 가진 원격 깃 리포지토리의 ‘main’ 브랜치로 로컬 깃 리포지토리의 ‘HEAD’영역에 commit된 파일들을 업로드 하라는 의미입니다.  ‘-u’ 옵션은 정확한 푸시를 위해 추적(tracking) 정보를 추가하기 위한 옵션입니다. 

git push -u origin main 

 

업로드 시 ‘403’에러 조치

다음과 같이 ‘403’에러가 나면서 업로드가 안될 수 있습니다. 원인은 PC에서 이전에 다른 GitHub 사용자의 리포지토리로 푸시한 기록이 있기 때문입니다. 아래 예에서는 'hiondal' 사용자의 리포지토리로 푸시 했는데 이전에 푸시한 'happycloudpak' 사용자의 리포지토리로 접근하려고 했기 때문에 에러가 발생한 겁니다.   

remote: Permission to hiondal/hello.git denied to happycloudpak.

fatal: unable to access 'https://github.com/hiondal/hello.git/': The requested URL returned error: 403

 

다음과 같이 원격 깃 리포지토리의 주소를 변경하고 소스를 변경한 후 다시 'add->commit->push'하십시오. 노란색 볼드체 부분은 본인 사용자명으로 바꾸셔야 합니다. 

 

git remote set-url origin https://hiondal@github.com/hiondal/hello.git
index.js에 빈 줄을 추가하던지 해서 소스를 변경 합니다.
git add .
git commit -m "푸시 에러 조치"
git push -u origin main 

 

github의 id와 암호를 물을것입니다. 

id는 본인의 github 계정을 입력하면 됩니다. 

암호는 github의 로그인 암호를 넣으면 안되고, github에 Access Token을 만들고 그 Token을 넣어야 합니다. 

아래 글을 참조하여 Access Token을 만들고 입력하십시오.

https://firstquarter.tistory.com/entry/Git-%ED%86%A0%ED%81%B0-%EC%9D%B8%EC%A6%9D-%EB%A1%9C%EA%B7%B8%EC%9D%B8-remote-Support-for-password-authentication-was-removed-on-August-13-2021-Please-use-a-personal-access-token-instead

 

[Git] 토큰 인증 로그인 + 자격 증명 - remote: Support for password authentication was removed on August 13, 2021. Plea

windows 기준. AWS EC2 git clone 시 발생한 에러. 8월 13일 부로 push, clone, pull 시 비밀번호 대신 토큰을 사용하게 변경됨. Personal access token 이용 로그인 방법 정리. 1. https://github.com/ 접속 + 로그인 2. 우측

firstquarter.tistory.com

 

이제 GitHub에서 리프레시를 해보면 다음과 같이 소스가 올라가 있는 것을 확인할 수 있습니다.

 

<팁>

vscode 기능을 이용한 git push

 

vscode는 편리하게 git push를 하는 기능을 제공 합니다. 

아래 글의 끝에 있는 'git push'부분을 참고 하십시오. 

https://happycloud-lee.tistory.com/205

</>

 

3.3 소스 내려받기

컨테이너 이미지를 만들 작업 가상 머신에서 GitHub의 원격 깃 리포지토리로부터 소스를 내려받기 합니다. 

다음과 같이 작업 디렉토리를 만들고 'git clone {원격 깃 리포지토리 주소}'명령을 이용 합니다. 노란색 볼드체 부분은 본인 GitHub 사용자명으로 바꾸십시오.

[root@osboxes ~]# mkdir -p ~/docker && cd ~/docker
[root@osboxes docker]# git clone https://github.com/hiondal/hello.git
Cloning into 'hello'...
remote: Enumerating objects: 10, done.
remote: Counting objects: 100% (10/10), done.
remote: Compressing objects: 100% (7/7), done.
remote: Total 10 (delta 4), reused 9 (delta 3), pack-reused 0
Unpacking objects: 100% (10/10), 1.10 KiB | 565.00 KiB/s, done.

 

작업 디렉토리 밑에 'hello'라는 디렉토리가 생겼을 겁니다. 그 디렉토리로 이동 해 소스가 잘 내려받기 되었는지 확인해보십시오.

[root@osboxes docker]# cd hello
[root@osboxes hello]# ls -al
total 8
drwxr-xr-x. 3 root root  54 Dec 27 08:50 .
drwxr-xr-x. 3 root root  19 Dec 27 08:50 ..
drwxr-xr-x. 8 root root 163 Dec 27 08:50 .git
-rw-r--r--. 1 root root 387 Dec 27 08:50 index.js
-rw-r--r--. 1 root root 201 Dec 27 08:50 package.json

 

'git pull'명령은 언제 사용하는 걸까요 ?

여러분이 PC에서 소스를 다시 업로드 한 경우 최신의 소스를 다시 내려받기할 때 사용합니다. 

PC에서 소스를 고친 후 다시 업로드 해보겠습니다. index.js의 메시지를 'hello node!'에서 'hello docker!' 고치고 원격 깃 리포지토리로 push하십시오. 

두 번째부터 푸시 할 때는 'git push'만 해도 됩니다. 

~/docker/hello main
❯ git add .  

~/docker/hello main*
❯ git commit -m "메시지 변경"
[main cba418d] 메시지 변경
1 file changed, 1 insertion(+)

~/docker/hello main ⇡
❯ git push

 

작업 가상 머신의 '~/docker/hello'디렉토리에서 최신 소스를 다시 내려받기 합니다. 

[root@osboxes docker]# cd ~/docker/hello
[root@osboxes docker]# git pull 



3.4 컨테이너 이미지 만들기

이제 드디어 컨테이너 이미지를 만들 차례 입니다. 

컨테이너 이미지를 만들기 위해서는 이미지 정의 파일이 필요 합니다. 

이 이미지 정의 파일의 기본 파일명은 Dockerfile입니다. 물론 파일명은 바꿔도 상관없습니다. 

PC에 Dockerfile을 만들고 다음과 같이 내용을 작성 합니다.

FROM node:slim

ARG MAIN_APP
ENV APPNAME ${MAIN_APP}
ENV USERNAME docker  

ENV ARTIFACTORY_HOME /home/${USERNAME}

# DON'T change USERNAME
# Add a docker user, make work dir
RUN adduser --disabled-password --gecos "" ${USERNAME} && \
  echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers && \
  mkdir -p ${ARTIFACTORY_HOME} && \
  chown ${USERNAME}:${USERNAME} ${ARTIFACTORY_HOME}

WORKDIR ${ARTIFACTORY_HOME}
COPY . ./

RUN npm install

# RUN as docker user
USER docker
CMD node ${APPNAME}



Dockerfile은 이미지를 어떻게 만들지 정의하는 파일이므로 매우 중요합니다. 

위 Dockerfile정도만 이해해도 왠만한 Dockerfile은 응용해서 만들 수 있을 겁니다. 

그럼 하나 하나 설명해보겠습니다. 

먼저 컨테이너가 어플리케이션 구동에 필요한 모든 것이 통합된 작은 서버라는 것을 다시 한번 상기해 주십시오. 그리고 컨테이너 이미지를 만들 때 소스+베이스 이미지Base image+라이브러리가 합쳐진다는 기본 개념을 생각하면서 Dockerfile을 이해 하면 쉬울 겁니다.  

 

 

'FROM'문에서는 OS와 WAS를 갖고 있는 베이스 컨테이너 이미지base container image를 정의합니다. 

FROM node:slim

그런데 베이스 이미지는 어디서 어떻게 찾을 가요 ?

도커사에서 운영하는 공식 이미지 레지스트리인 https://hub.docker.com(이하 도커 허브Docker HUB)에서 찾으면 됩니다. 

 

Node.js, Vue.js, React.js의 베이스 이미지는 'node'이고, java는 보통 'openjdk'입니다. 

Docker HUB에서 베이스 이미지명으로 검색해 공인된 리포지토리를 찾으십시오. 그리고 ‘tags’탭에서 사용할 적절한 버전을 선택하면 됩니다. 

예를 들면 'node' 리포지토리에서 태그tag를 보면 다음과 같이 용량이 나옵니다. 각 태그를 누르면 보다 자세한 정보가 나옵니다. 이 정보를 참조해 베이스 이미지를 결정하면 됩니다. 

 

'ARG'문은 이미지를 만들 때 인수로 받을 변수명입니다. Node.js, Vue.js, React.js는 메인 프로그램 이름이 다를 수 있으므로 이미지를 만들 때 메인 프로그램명을 인수로 받는 게 좋습니다. 인수를 넘기는 방법은 'docker build --build-arg MAIN_APP=index …'와 같이 '--build-arg'옵션을 사용합니다. 

'ENV'문은 이미지가 컨테이너로 실행될 때 컨테이너 내부에 생성될 환경 변수명을 지정합니다. 또한 Dockerfile의 뒷부분에서도 이 환경변수값을 참조할 수 있습니다. 

이 예에서는 메인 프로그램명, 실행 유저명, HOME디렉토리명을 환경 변수로 선언했습니다. 

ARG MAIN_APP
ENV APPNAME ${MAIN_APP}
ENV USERNAME docker  

ENV ARTIFACTORY_HOME /home/${USERNAME}

 

'RUN'문은 이미지를 만들 때 수행할 명령을 지정 합니다. 이 Dockerfile에서는 'RUN'문이 두 번 사용되는데 첫 번째 'RUN'문은 'docker'라는 OS 사용자 계정을 만들고 HOME 디렉토리를 만드는 명령입니다. 

'adduser'라는 명령으로 'docker'라는 OS 사용자 계정을 만듭니다. '--disabled-password'는 암호 없이 만들겠다는 것이고 '--gecos'는 OS 사용자 계정을 만들 때 기본적으로 물어보는 사용자 전체 이름fullname이나 전화번호를 묻지 않게 하는 옵션입니다. 

<주의>

도커 데몬으로 컨테이너를 실행하기 위해서 유저명은 반드시 ‘docker’여야 합니다. 

</>

'/etc/sudoers'에 echo명령으로 추가하는 것은 docker 사용자에게 root 권한을 부여하는 겁니다. 

그다음 위에서 환경 변수로 만든 '${ARTIFACTORY_HOME}'값을 참조해 HOME디렉토리를 만듭니다. 

마지막으로 'docker' user가 HOME디렉토리의 owner가 되도록 디렉토리 권한을 수정 합니다. 

# Add a docker user, make work dir
RUN adduser --disabled-password --gecos "" ${USERNAME} && \
  echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers && \
  mkdir -p ${ARTIFACTORY_HOME} && \
  chown ${USERNAME}:${USERNAME} ${ARTIFACTORY_HOME}

그런데 왜 RUN문을 '&&'로 계속 이어서 정의 했을까요 ? 

그 이유를 이해하려면 컨테이너 이미지가 레이어layer구조로 만들어 진다는 것부터 알아야 합니다. 

자세한 설명은 아래 글을 참고 하시고 일단 이미지가 여러 개의 레이어로 나누어서 만들어진다는 것과 그 이유가 이미지 레지스트리에서 내려받기 하거나 이미지 레지스트리로 업로드 할 때 성능을 빠르게 하기 위해서라는 것만 알아 두시기 바랍니다. 

https://happycloud-lee.tistory.com/241

'RUN'문은 수행 될 때마다 이미지의 레이어가 하나씩 생깁니다. 불필요하게 레이어가 생기지 않으려면 위 예처럼 '&&'로 연결해서 정의하면 됩니다. 

 

'WORKDIR'문은 작업 디렉토리를 지정하는 겁니다. 작업 디렉토리는 이후 수행에서 현재 디렉토리가 됩니다. 

'COPY'문은 이미지안으로 파일을 복사하는 명령입니다. 첫 번째 인수는 이미지를 빌드할 때 지정한 디렉토리이고 두 번째 인수는 이미지안의 작업 디렉토리입니다. 

'docker build ….. {소스가 있는 제일 상위 디렉토리}'라고 했다면 첫 번째 인수를 '.'으로 했으므로 '소스가 있는 제일 상위 디렉토리와 그 서브의 모든 파일'들이 이미지안의 작업 디렉토리인 '${ARTIFACTORY_HOME}'으로 복사 됩니다.  

WORKDIR ${ARTIFACTORY_HOME}
COPY . ./

 

모든 소스가 복사 된 후 'npm install'명령을 실행해 필요한 라이브러리를 이미지안에 설치합니다. 

필요한 라이브러리 정보는 어디에서 읽을까요 ? 

Node.js, Vue.js, React.js는 package.json파일 안의 'dependencies'항목 밑에 사용하는 라이브러리를 정의 합니다. 'npm install'명령이 수행될 때 이 정보를 이용해 라이브러리를 설치합니다. 

 

RUN npm install

 

'USER'문은 이후에 수행할 명령을 실행할 사용자 계정을 정의 합니다. 여기서는 위에서 만든 'docker' 사용자로 실행하겠다고 정의 했습니다. 

'CMD'문은 컨테이너가 실행될 때 수행할 명령을 정의 합니다. 

# RUN as docker user
USER ${USERNAME}
CMD node ${APPNAME}

 

CMD와 ENTRYPOINT 차이

위 CMD명령을 ENTRYPOINT로 바꿔도 정상적으로 실행 됩니다. 

두 명령의 차이는 CMD로 정의한 명령은 컨테이너가 실행될 때 바뀔 수 있지만 ENTRYPOINT로 정의한 명령은 바뀌지 않고 반드시 실행된다는 겁니다. 

예를 들어 CMD로 정의한 명령은 'docker run ….. {실행 명령}'형식으로 컨테이너를 실행하면 맨 뒤에 있는 '{실행 명령}'으로 바뀌고 Dockerfile에 정의한 CMD명령은 무시 됩니다.

ENTRYPOINT로 정의한 명령은 'docker run ….. {실행 명령}'형식으로 컨테이너를 실행하면 ENTRYPOINT로 정의한 명령 뒤에 '{실행 명령}이 붙어서 실행 됩니다. 

 

CMD와 ENTRYPOINT를 대괄호로 지정하기

다음과 같은 형식으로도 지정할 수 있습니다. '/bin/sh', '-c'를 생략하면 에러가 납니다. 

CMD [ "/bin/sh", "-c", "node ${APPNAME}" ]

ENTRYPOINT [ "/bin/sh", "-c", "node ${APPNAME}" ]

 

Dockerfile에 대해서는 아래 글에도 잘 정리가 되어 있으니 참고 하십시오. 

https://rampart81.github.io/post/dockerfile_instructions/

 

이제 진짜로 컨테이너 이미지를 만들 시간 입니다. 

먼저 vscode의 터미널에서 다음과 같이 새로 추가된 Dockerfile을 GitHub로 푸시합니다. 

~/docker/hello main
❯ git add .  

~/docker/hello main*
❯ git commit -m "Dockerfile 추가"

~/docker/hello main ⇡
❯ git push

 

작업가상 머신에서 최신 소스를 다시 가져 옵니다. git config명령은 최신 소스를 가져올 때 경고warning제거를 위해 수행했으며 한 번만 수행하면 됩니다. 

[root@osboxes docker]# cd ~/docker/hello
[root@osboxes docker]# git config pull.rebase false
[root@osboxes docker]# git pull 

 

작업 가상 머신에서 다음과 같이 'docker build'명령을 이용해 컨테이너 이미지를 만듭니다. 

'--build-arg' 옵션을 이용해 메인 프로그램명을 넘겼습니다. 

'-f' 옵션은 이미지 정의 파일을 지정하는 옵션입니다. 사실 기본 파일명인 'Dockerfile'을 사용한 경우는 생략해도 됩니다. 

'-t'옵션 뒤에 이미지의 전체 경로명을 지정합니다. 

마지막의 '.'은 소스가 있는 최상위 디렉토리입니다. 현재 디렉토리이므로 '.'이라고 지정 했습니다.  

다음과 같이 이미지가 Dockerfile에 지정한 대로 생성되는 것을 확인할 수 있습니다. 

노란색 볼드체는 본인의 것으로 바꿔야 하는데 어떻게 바꿔야 할 지는 조금 후 설명할 이미지 레지스트의 구조를 이해하면 아시게 될 겁니다. 

[root@osboxes docker]# docker build --build-arg MAIN_APP=index -f Dockerfile -t docker.io/hiondal/hello:1.0.0 .

Sending build context to Docker daemon  109.1kB
Step 1/10 : FROM node:slim
---> a6a0d486ccb2
Step 2/10 : ARG MAIN_APP
---> Using cache
---> a4b4956c811d
Step 3/10 : ENV APPNAME ${MAIN_APP}
---> Using cache
---> fe355f0eb1c6
Step 4/10 : ENV ARTIFACTORY_HOME /home/docker
---> Using cache
---> 6555a6bda689
Step 5/10 : RUN adduser --disabled-password --gecos "" docker &&   echo "docker ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers &&   mkdir -p ${ARTIFACTORY_HOME} &&   chown docker:docker ${ARTIFACTORY_HOME}
---> Using cache
---> bdbd2e83c8bb
Step 6/10 : WORKDIR ${ARTIFACTORY_HOME}
---> Using cache
---> 91bf613a2962
Step 7/10 : COPY . ./
---> Using cache
---> 158a28ba820d
Step 8/10 : RUN npm install
---> Using cache
---> f976d807b11c
Step 9/10 : USER docker
---> Using cache
---> 47b99a60b979
Step 10/10 : ENTRYPOINT node ${APPNAME}
---> Using cache
---> 2981cd61b29c
Successfully built 2981cd61b29c
Successfully tagged docker.io/hiondal/hello:1.0.

 

이미지 레지스트리의 구조를 쉽게 이해하려면 창고 구조를 생각하면 됩니다. 

도커 아키텍처를 설명할 때 예로 들었던 컨테이너하우스 창고를 상상해보십시오. 

컨테이너하우스 창고를 열고 들어가면 '고급형', '보급형'과 같이 컨테이너하우스들을 크게 분류한 공간이 있을 겁니다. 각 분류 공간 내에는 컨테이너하우스들이 제품별로 나뉘어져 있고 각 제품은 모델별로 정리가 되어 있을 겁니다. 

이미지 레지스트리도 같은 구조 입니다. 컨테이너 창고가 이미지 레지스트리가 되고 일정한 기준으로 분류된 공간은 '오거니제이션organization'이라고 부릅니다. 각 'organization'별로 이미지가 저장된 단위는 '리포지토리repository'가 되고 리포지토리는 여러 개의 버전을 의미하는 '태그tag'로 나누어 저장됩니다. 

 

 

실습에서 사용할 이미지 레지스트리는 도커사에서 운영하는 도커허브Docker HUB입니다. 그 사이트 주소는 https://hub.docker.com이고 이미지 경로를 지정할 때는 'docker.io'라는 주소를 사용합니다. 

이미지 전체 경로를 정의할 때는 이 이미지 레지스트리의 구조와 동일하게 지정하면 됩니다. 

그래서 위 예에서 지정한 이미지 전체 경로 'docker.io/hiondal/hello:1.0.0'은 다음과 같이 분류할 수 있습니다. 

- docker.io : 이미지 레지스트리

- hiondal: 오거니제이션

- hello: 리포지토리

- 1.0.0: 태그

 

Git Registry의 구조

GitHub, GitLab, Bitbucket 등 깃 레지스트리의 구조도 이미지 레지스트리와 유사합니다. 

github.com, gitlab.com과 같은 주소가 레지스트리입니다. 

Git도 각 리포지토리를 분류한 오거니제이션organization이 있습니다. 기본 오거니제이션은 가입 시 등록한 사용자명이고 추가할 수 있습니다. 

오거니제이션 서브에는 리포지토리가 위치 합니다. 여러분이 만든 'hello.git'이 리포지토리입니다. 

이미지 리포지토리의 '태그tag'는 Git에서 '브랜치branch'라고 할 수 있습니다. 

 

이미지를 도커 허브Docker HUB에 저장하려면 여러분들도 도커 허브에 오거니제이션을 만들어야 합니다. 

https://hub.docker.com으로 접근해 회원가입을 하십시오. 회원 가입시 사용한 사용자명이 여러분의 기본 오거니제이션이 됩니다. 물론 필요에 따라 오거니제이션은 더 만들 수도 있습니다. 

이제 여러분의 도커 허브 사용자명으로 이미지경로를 바꾸어 이미지를 만드십시오. 

예를 들어 도커 허브 사용자명이 'happydocker'라면 다음과 같이 만드시면 됩니다. 

docker build --build-arg MAIN_APP=index -f Dockerfile -t docker.io/happydocker/hello:1.0.0 .

 

3.5 컨테이너 이미지 생성 확인

컨테이너 이미지가 제대로 생성되었는지를 보려면 'docker images'라는 명령을 사용 합니다. 

[root@osboxes hello]# docker images
REPOSITORY               TAG       IMAGE ID       CREATED         SIZE
hiondal/hello            1.0.0     2981cd61b29c   2 hours ago     243MB

 

<팁>

이미지 전체 경로에서 생략할 수 있는 부분

이미지 전체 경로에서 레지스트리registry, 오거니제이션organization, 태그tag를 생략하고 리포지토리repository부분만 지정해도 됩니다. 

도커 아키텍처 실습에서 사용했던 이미지 'ghost'는 리포지토리만 지정한 예입니다. 

레지스트리를 생략하면 도커 허브 주소인 'docker.io'를 의미합니다. 위에서 컨테이너 이미지 리스트를 확인 했을 때 ‘REPOSITORY’에 'hiondal/hello'만 나온 이유는 'docker.io'가 기본 레지스트리이기 때문에 생략해서 표시된 겁니다. 

오거니제이션organization을 생략하면 docker.io의 'library' 리포지토리를 의미 합니다. 도커사에서 공인하는 공식적인 이미지가 'library' 오거니제이션에 저장 됩니다. 여러분이 만드는 이미지를 레지스트리에 푸시 하려면 반드시 오거니제이션을 여러분의 도커 허브 사용자명이나 여러분이 도커 허브에 만든 오거니제이션으로 지정해야 합니다. 

태그tag를 생략하면 'latest'라는 태그가 자동으로 부여 됩니다. 

예를 들어 이미지 경로를 'hiondal/hello'라고만 지정했다면 실제 전체 경로는 'docker.io/hiondal/hello:latest'가 됩니다. 

</>

 

<팁>

'docker tag' 활용하기

이미지명이 너무 길어서 불편하면 일단은 리포지토리 부분만 지정해 만들고 이미지 레지스트리에 푸시 하기 전에 'docker tag'명령을 이용해 링크 이미지를 만들 수도 있습니다. 

 

예를 들어 다음과 같이 이미지를 만들 수도 있습니다. 

$ docker build --build-arg MAIN_APP=index -t hello .

그리고 이미지 레지스트리에 푸시 하기 전에 docker tag 명령어로 바꿔주면 됩니다.

$ docker tag hello docker.io/hiondal/hello:1.0.0

</>

 

4 컨테이너 이미지 푸시

 

컨테이너 이미지 푸시는 전체 순서에서 ❺ Registry 로그인과 ❻ 이미지 업로드를 수행하는 단계 입니다. 

 

 

4.1 이미지 레지스트리 로그인

이미지를 푸시하려면 이미지 레지스트리에 로그인부터 해야 합니다. 

'docer login {registry 주소}' 명령을 사용해 로그인 합니다. 

이미지 레지스트리 주소를 생략하면 docker.io로 로그인한다는 의미 입니다. 명시적으로 docker login docker.io로 해도 됩니다. 

다음과 같이 도커 허브에 가입시 등록한 사용자명과 암호로 로그인 합니다. 

[root@osboxes hello]# docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: hiondal
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

 

4.2 이미지 업로드

'docker push {image 전체 경로}'명령을 이용해 이미지를 레지스트리에 업로드 합니다. 

이미지의 경로가 맞는지 확인하고 업로드 합니다. 아래 노란색 볼드체는 본인의 도커 허브 사용자명 또는 오거니제이션으로 바꾸셔야 합니다.  

[root@osboxes hello]# docker push docker.io/hiondal/hello:1.0.0
The push refers to repository [docker.io/hiondal/hello]
a9a1ab52037e: Pushed
9ccc65c3652b: Pushed
fa7537101c26: Pushed
480780c1297d: Pushed
8aee3140ddb6: Pushed
e30d94d2a229: Pushed
197720a4c240: Pushed
2edcec3590a4: Pushed
1.0.0: digest: sha256:f7d922a6df4b223c98a0f7a4d667b8206b110ac847



5. 컨테이너 실행 및 관리

이제 서비스를 제공할 머신이나 가상 머신에서 컨테이너를 실행하면 됩니다. 

실습에서는 컨테이너 이미지를 만든 가상 머신 하나밖에 없기 때문에 그 가상 머신에서 컨테이너를 실행하겠습니다.

실무에서는 보통 컨테이너 이미지를 만드는 머신과 컨테이너를 실행하는 머신은 다릅니다. 

이 단계는 아래와 같이 실습 합니다.

❼ 이미지 다운로드: 컨테이너를 실행할 가상머신으로 컨테이너 이미지 다운로드

❽ 컨테이너 실행: 이미지를 이용하여 컨테이너 실행

❾ 컨테이너 프로세스 확인: 컨테이너가 정상적으로 실행 되었는지 프로세스 확인

❿ 중지: 실행중인 컨테이너를 중지

⓫ 시작: 중단된 컨테이너를 시작    

 

5.1 이미지 내려받기

PC의 가상 머신으로 컨테이너 이미지를 내려받기 합니다. 

실제로는 컨테이너를 실행해 서비스를 제공할 물리머신이나 가상 머신에서 할 작업입니다. 

노란색 볼드체는 본인의 도커 허브 오거니제이션으로 바꾸시고 수행하십시오. 

아래 예처럼 내려받기가 되긴 하는데 너무 빨리 끝날 겁니다. 왜냐하면 이미 가상 머신에 그 image가 있기 때문입니다. 

[root@osboxes .ssh]# docker pull docker.io/hiondal/hello:1.0.0
1.0.0: Pulling from hiondal/hello
Digest: sha256:f7d922a6df4b223c98a0f7a4d667b8206b110ac847f336f6dd5f3e34535bd4b4
Status: Image is up to date for hiondal/hello:1.0.0
docker.io/hiondal/hello:1.0.0

 

가상 머신에 있는 image를 지우고 다시 내려받기 해봅니다.

[root@osboxes .ssh]# docker rmi docker.io/hiondal/hello:1.0.0
[root@osboxes .ssh]# docker pull docker.io/hiondal/hello:1.0.0

 

이번에는 아래 예처럼 내려받기에 시간도 더 걸리고 뭔가 나누어서 내려받기가 되는 것을 볼 수 있을 겁니다. 

이 나뉘어진 각각이 이미지의 레이어입니다. 아래 예에서 'hello' image는 8개의 레이어로 구성됐다는 것을 알 수 있습니다. 

1.0.0: Pulling from hiondal/hello
a2abf6c4d29d: Pull complete
716b0e096692: Pull complete
d65f0cc0c2e2: Pull complete
c7fbf3401fad: Pull complete
811ff2aaa8ce: Pull complete
22a6a0d26b1c: Pull complete
ecf89e85ef4c: Pull complete
4ded74f02cd7: Pull complete
Digest: sha256:f7d922a6df4b223c98a0f7a4d667b8206b110ac847f336f6dd5f3e34535bd4b4
Status: Downloaded newer image for hiondal/hello:1.0.0
docker.io/hiondal/hello:1.0.0 

 

5.2 컨테이너 실행

내려받은 이미지를 컨테이너로 실행 합니다. 

docker run <옵션> <이미지 전체 경로>

 

실행할 때 여러 가지 옵션을 줄 수 있는데 가장 많이 사용하는 것들은 다음과 같습니다. 

--name: 컨테이너의 이름을 지정. 생략 시 랜덤하게 부여됨

-it: 포그라운드foreground로 실행하고, 서버 콘솔 결과를 터미널에 표시함

-d: 백그라운드background로 실행함

--rm: 컨테이너를 중지하면 컨테이너를 자동으로 삭제해 불필요한 프로세스를 제거 

-e <key>=<value>: 컨테이너안에 환경 변수를 넘김

-p <외부포트>:<내부포트>: 외부에 노출할 포트와 그 포트와 연결된 내부포트를 지정함

 

우리가 실행할 'hello'어플리케이션은 환경 변수는 필요 없고 포트는 지정해야 합니다. index.js에서 서버가 수신하는 포트를 다음과 같이 '8080'으로 했으니 내부포트는 '8080'이 됩니다. 외부포트는 가상 머신에서 이미 사용중인 포트가 아니라면 어떤 것이든 상관없습니다. 

...
var port = 8080;

http.createServer( (req, res) => {
    ...
}).listen(port, () => {
  console.log('Listen ... ' + os.hostname()+':'+port);
});

 

포그라운드로 먼저 실행해보겠습니다. 아래 명령을 실행하면 콘솔의 프람프트가 떨어지지 않고 대기합니다. 그리고 index.js에서 구현했던 콘솔 로그가 터미널에 표시 됩니다. 

[root@osboxes .ssh]# docker run --name hello -it -p 8888:8080 hiondal/hello:1.0.0
Listen ... 34088d924ad7:8080

 

PC에서 웹브라우저를 열고 http://{가상 머신 IP}:8888로 접근 하십시오. 다음과 같이 hello서비스 페이지가 나오면 성공입니다. 

 

이번에는 백그라운드로 다른 컨테이너를 실행해보겠습니다. 옵션 '-it'를 '-d'로 바꾸면 됩니다. 

아마 에러가 발생할 겁니다. 위에서 실행한 컨테이너가 중지는 되었지만 아직은 프로세스가 남아 있어서 같은 이름의 컨테이너가 있다는 에러 입니다. 

[root@osboxes ~]# docker run --name hello -d -p 8888:8080 hiondal/hello:1.0.0
docker: Error response from daemon: Conflict. The container name "/hello" is already in use ...

 

이름을 바꿔서 다시 실행 합니다. 외부 포트 번호는 안 바꿔도 되지만 나중에 2개의 컨테이너를 한꺼번에 실행할 것이므로 충돌 안 나게 '9999'로 바꿔 줍니다. 

이번에는 잘 실행이 되고 백그라운드로 실행 되었기 때문에 프람프트가 바로 떨어 집니다. 웹브라우저에서 다시 접근해보면 서비스도 정상적으로 표시 됩니다. 

[root@osboxes ~]# docker run --name hello2 -d -p 9999:8080 hiondal/hello:1.0.0
28f505ff0699ecb86bd3885cbebbf644ac68d6c55c2502535ad5c656cb1b74d0
[root@osboxes ~]#



5.3 컨테이너 프로세스 확인

실행 중인 컨테이너와 중지된 컨테이너를 확인할 수 있어야겠죠 ? 

'docker ps'명령으로 컨테이너 프로세스를 확인할 수 있습니다.  명령을 수행하면 다음과 같이 현재 실행 중인 'hello2'컨테이너만 나옵니다. 

[root@osboxes ~]# docker ps
CONTAINER ID   IMAGE                 COMMAND                  CREATED          STATUS          PORTS                                       NAMES
d8b280f36dac   hiondal/hello:1.0.0   "/bin/sh -c 'node ${..."   15 minutes ago   Up 15 minutes   0.0.0.0:9999->8080/tcp, :::9999->8080/tcp   hello2

 

앞에 실행했다 중지한 'hello'컨테이너 프로세스는 어떻게 확인할까요 ?

'docker ps -a'와 같이 '-a'옵션을 붙이면 전체 컨테이너 프로세스를 확인할 수 있습니다. 

[root@osboxes ~]# docker ps -a
CONTAINER ID   IMAGE                 COMMAND                  CREATED          STATUS                        PORTS                                       NAMES
d8b280f36dac   hiondal/hello:1.0.0   "/bin/sh -c 'node ${..."   19 minutes ago   Up 19 minutes                 0.0.0.0:9999->8080/tcp, :::9999->8080/tcp   hello2
48cc133a9cf3   hiondal/hello:1.0.0   "/bin/sh -c 'node ${..."   29 minutes ago   Exited (130) 21 minutes ago                                               hello

 

컨테이너 프로세스 결과 리스트를 보면 프로세스 번호(Container ID), 사용한 이미지(Image), 시작한 후 경과 시간(Created), 컨테이너의 상태(Status), 포트 번호(Ports), 컨테이너 이름(Names)가 나옵니다. 

특히 Container ID와 Name은 Container를 조작할 때 중요한 정보로 사용됩니다. 

 

5.4 컨테이너 중지/시작

컨테이너 프로세스를 중지하려면 다음과 같이 'docker stop {container id 또는 name}'명령을 사용하면 됩니다. 

'hello2'컨테이너를 중지해보겠습니다. 

아래 예에서는 컨테이너 이름인 'hello2'를 사용했습니다. 'docker stop d8b280f36dac'와 같이 컨테이너 ID를 사용해 중지시킬 수도 있습니다. 

정지된 컨테이너는 'docker ps -a'로 확인할 수 있습니다. 아래 예처럼 '--format'옵션을 사용해서 보기 좋게 필요한 항목만 볼 수도 있습니다. 

[root@osboxes ~]# docker stop hello2
hello2
[root@osboxes ~]# docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
[root@osboxes ~]# docker ps -a --format "table {{.ID}}\t{{.Image}}\t{{.Names}}\t{{.Status}}"
CONTAINER ID   IMAGE                 NAMES     STATUS
d8b280f36dac   hiondal/hello:1.0.0   hello2    Exited (137) 44 seconds ago
48cc133a9cf3   hiondal/hello:1.0.0   hello     Exited (130) 38 minutes ago

 

<팁>

컨테이너 ID편하게 사용하기 

컨테이너 ID를 지정할 때 구별만 된다면 앞에 일부 값만 지정해도 됩니다. 

예를 들어 'hello2'를 정지시킬때 'docker stop d8'이라고만 해도 됩니다. 'd8'로 시작하는 컨테이너는 1개만 있기 때문입니다.

</>

 

<팁>

컨테이너 실행 시 '--rm' 옵션 활용하기

컨테이너를 중지해도 아직 프로세스만 중지된 거지 컨테이너가 완전히 삭제된 것은 아닙니다. 

실무에서는 불필요한 쓰레기 프로세스가 남을 수 있습니다. 

컨테이너 실행 시 '--rm' 옵션을 붙이면 컨테이너 프로세스 중지 시 자동으로 컨테이너를 완전히 삭제할 수 있습니다. 

아래 예와 같이 새로운 컨테이너를 '--rm'옵션으로 실행한 후 중지하고 컨테이너가 자동으로 삭제되는 지 확인해보십시오. 

[root@osboxes ~]# docker run --name hello3 -d -p 9991:8080 --rm hiondal/hello:1.0.0
[root@osboxes ~]# docker stop hello3
[root@osboxes ~]# docker ps -a | grep hello3

</>



중지된 컨테이너를 다시 시작해보겠습니다. 

아래 예와 같이 'docker start {container id or name}'을 이용하면 되며 여러 개의 컨테이너를 한꺼번에 시작할 수 있습니다. 

[root@osboxes ~]# docker ps -a --format "table {{.ID}}\t{{.Names}}"
CONTAINER ID   NAMES
d8b280f36dac   hello2
48cc133a9cf3   hello
[root@osboxes ~]# docker start d8 hello
d8
hello
[root@osboxes ~]# docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Status}}"
CONTAINER ID   NAMES     STATUS
d8b280f36dac   hello2    Up 21 seconds
48cc133a9cf3   hello     Up 16 seconds

 

더 이상 필요 없는 컨테이너를 완전히 삭제할 수도 있어야 겠죠 ? 

이때는 'docker rm {container id or name}'명령을 사용하면 됩니다. 

주의할 것은 실행 중인 컨테이너는 정지를 먼저 하고 삭제할 수 있다는 겁니다. 

아래 예는 hello2 컨테이너를 완전히 삭제하는 예시입니다. 

[root@osboxes ~]# docker rm d8
Error response from daemon: You cannot remove a running container d8b280f36dac1082eb1d2f42b9df522baa745cfc9ec1d1ebd081696fc359c12c. Stop the container before attempting removal or force remove
[root@osboxes ~]# docker stop d8
d8
[root@osboxes ~]# docker rm d8
d8



5.5 그 외 자주 사용하는 명령어들

지금까지 개발한 어플리케이션을 컨테이너 이미지로 만들어 이미지 레지스트리에 푸시 하고, 그 이미지를 이용해 컨테이너로 실행하는 방법을 실습했습니다. 

마지막으로 도커 명령 중 가장 자주 사용하는 나머지 명령어들을 배워 보겠습니다. 

도커도 상당히 복잡한 기술이어서 이 외에도 스토리지 사용을 위한 volume과 네트워크 사용을 위한 network 명령 등 더 알아야 할 것들이 많습니다. 

하지만 쿠버네티스를 사용해 컨테이너를 배포하고 운영할 생각이라면 이 정도만 알아도 충분 합니다. 


1) 실행 중인 컨테이너의 로그를 보는 방법은 ? 

docker logs [-f] <container id or name>

 

[root@osboxes ~]# docker logs -f hello
실시간으로 콘솔로그를 터미널에 보여줍니다.


[root@osboxes ~]# docker logs --tail 10 hello
가장 최신 10줄을 보여줍니다.


* Log파일 위치는 다음과 같이 확인하면 됩니다.
[root@osboxes ~]# docker inspect hello | grep LogPath



2) 실행 중인 컨테이너 안에 telnet으로 어떻게 접근할까 ?

docker exec -it <container id or name> <sh 또는 bash>

 

컨테이너 'hello' 안으로 들어가 봅시다. 컨테이너안의 OS종류에 따라서 sh 또는 bash명령을 이용하면 됩니다. 

[root@osboxes ~]# docker exec -it hello sh

exec라는 명령은 컨테이너에게 어떤 명령을 내리는 겁니다. 이를 응용해 OS쉘을 실행시키는 방법을 쓰면 됩니다.

 

아래 예처럼 Dockerfile에 지정한 home디렉토리로 들어가게 됩니다. 그 안에는 Dockerfile에서 'COPY'문으로 복사한 파일들이 있는 걸 볼 수 있습니다. 

[root@osboxes ~]# docker exec -it hello sh
$ pwd
/home/docker
$ ls
Dockerfile  index.js  package-lock.json  package.json



3) 실행 중인 컨테이너의 제반 정보(CPU, Memory, IP, Port 등)를 어떻게 볼까?

docker inspect <container id or name>

 

[root@osboxes ~]# docker inspect hello

 

inspect명령은 컨테이너 뿐 아니라, 모든 도커 객체유형(image, network, volume등)의 정보를 볼 때 사용할 수 있습니다. 아래는 이미지의 정보를 inspect명령으로 보는 예제입니다. 

[root@osboxes ~]# docker images
REPOSITORY      TAG       IMAGE ID       CREATED        SIZE
hiondal/hello   1.0.0     2981cd61b29c   16 hours ago   243MB
[root@osboxes ~]# docker inspect 298
[
    {
        "Id": "sha256:2981cd61b29c4d9f5b6696d9d4e15fca5810c71d42614f249ebc367f42804052",
        "RepoTags": [
            "hiondal/hello:1.0.0"
        ],

 

4) Host머신의 디렉토리를 컨테이너 안으로 마운트 시키기

Host머신의 디렉토리를 컨테이너 안으로 마운트하는 파라미터는 '--mount'입니다. 

사용문법: --mount type=bind,source={Host머신 디렉토리},target={컨테이너의 마운트할 디렉토리} 

주의) mount뒤의 값들은 쉼표로 구분되어야 하고 쉼표 다음에 스페이스가 있으면 안됨 

 

테스트 할 이미지를 내려 받습니다. 

docker pull hiondal/recommend

 

이 프로그램은 SpringBoot로 제작된 간단한 프로그램입니다. 

실행을 위해서는 아래와 같은 처리가 필요 합니다. 

구분 내용
포트 어플리케이션 내부 사용 포트인 3002번과 포트 바인딩 필요 
환경변수 PROFILE
어떤 프로파일 설정으로 어플리케이션을 구동할 지 지정하는 변수로 예제에서는 아무값이나 지정하면 됨 (예: dev, staging, prod)
디렉토리 마운트 어플리케이션은 /home/docker/data디렉토리의 'bestott.properties'라는 파일을 참조함
VM에 적절할 위치에 bestott.properties디렉토리를 만들고 이 디렉토리를 컨테이너 안의 /home/docker/data디렉토리로 마운트 해야 함 

먼저 VM의 현재 사용자 Home디렉토리 아래에 'data'디렉토리를 만들고 그 안에 bestott.properties파일을 다운로드 합니다. 

> mkdir -p ~/data && cd ~/data
> wget hiondalott.github.io/ott/bestott.properties
> cat bestott.properties
ondal=Netflix,https://www.netflix.com
user1=Disney+,https://www.disneyplus.comm
user2=WATCHA,https://watcha.com

 

컨테이너를 실행합니다. 

docker run -d --rm --name recommend \
-p 8002:3002 \
-e PROFILE=dev \
--mount type=bind,source=$HOME/data,target=/home/docker/data \
hiondal/recommend

프로세스를 확인합니다. -> docker ps | grep recommend 

컨테이너 내부로 들어가서 /home/docker/data에 파일이 제대로 있는지도 봅니다. 

> docker exec -it recommend sh 
$ ls -al /home/docker/data

 

브라우저에서 정상적으로 결과가 나오는지 확인합니다. 

http://{VM public IP}:8002/bestott/ondal 

 


5) 실행 중인 컨테이너로 파일을 복사하거나, 컨테이너 안의 파일을 가져올 수 있는가 ?

docker cp <소스 파일경로> <container id or name>:<타깃파일경로>

docker cp <container id or name>:<소스 파일경로> <타깃파일경로>

 

hello컨테이너안의 홈디렉토리로 파일을 복사하고 'docker exec'명령으로 확인해보십시오. 

[root@osboxes ~]# echo "hello" > README
[root@osboxes ~]# docker cp README hello:/home/docker
[root@osboxes ~]# docker exec -it hello ls
Dockerfile  README  index.js  package-lock.json  package.json
[root@osboxes ~]# docker exec -it hello cat ./README
hello

 

반대로 컨테이너 안의 파일을 Host 가상 머신으로 복사할 수도 있습니다. 

위에서 컨테이너 내에 복사한 'README'파일을 가상 머신에 파일명을 다르게 해 복사해보십시오.

[root@osboxes ~]# docker cp hello:/home/docker/README ./READYOU
[root@osboxes ~]# cat READYOU
hello



6) 필요 없는 이미지를 완전히 삭제하는 방법은 ? 

docker rmi <image 경로 or name>

 

지우려는 이미지를 사용한 컨테이너가 실행되고 있다면 삭제가 안 됩니다. 

그 이미지를 사용하는 컨테이너들을 먼저 중지하고 완전히 삭제해야 합니다.  

실습은 아래 6)번을 하면서 같이 하겠습니다. 


7) 이미지 레지스트리 없이 이미지를 다른 서버로 복사할 수는 없는가?

docker save <image id or path> -o <압축파일경로>

docker load -i <압축파일경로>

 

이 명령들은 인터넷을 사용할 수 없는 내부 패쇄망의 서버로 이미지를 복사할 때 많이 사용 합니다. 

먼저 이미지가 있는 물리 머신이나 가상 머신에서 반입할 이미지를 압축파일로 만듭니다. 

[root@osboxes ~]# docker save hiondal/hello:1.0.0 -o hello.tar
[root@osboxes ~]# ls -al | grep hello
-rw-------.  1 root root 251282432 Dec 28 02:16 hello.tar

 

테스트를 위해 이미지를 삭제 합니다. 삭제 시 이미지 경로(예: hiondal/hello:1.0.0)를 써도 되지만 이미지 ID를 사용해도 됩니다. 

다음과 같이 컨테이너 'hello'를 먼저 중지하고 삭제해야 이미지를 삭제할 수 있습니다. 

[root@osboxes ~]# docker images
REPOSITORY      TAG       IMAGE ID       CREATED        SIZE
hiondal/hello   1.0.0     2981cd61b29c   16 hours ago   243MB
[root@osboxes ~]# docker rmi 298
Error response from daemon: conflict: unable to delete 2981cd61b29c (cannot be forced) - image is being used by running container 48cc133a9cf3
[root@osboxes ~]# docker ps
CONTAINER ID   IMAGE                 COMMAND                  CREATED          STATUS          PORTS                                       NAMES
48cc133a9cf3   hiondal/hello:1.0.0   "/bin/sh -c 'node ${..."   59 minutes ago   Up 52 minutes   0.0.0.0:8888->8080/tcp, :::8888->8080/tcp   hello
[root@osboxes ~]# docker stop hello
hello
[root@osboxes ~]# docker rm hello
hello
[root@osboxes ~]# docker rmi 298
Untagged: hiondal/hello:1.0.0
Untagged: hiondal/hello@sha256:f7d922a6df4b223c98a0f7a4d667b8206b110ac847f336f6dd5f3e34535bd4b4
Deleted: sha256:2981cd61b29c4d9f5b6696d9d4e15fca5810c71d42614f249ebc367f42804052
....
Deleted: sha256:2edcec3590a4ec7f40cf0743c15d78fb39d8326bc029073b41ef9727da6c851f

 

이제 압축파일로부터 이미지를 로딩 합니다. 

아래 예와 같이 이미지가 생성되는 것을 확인할 수 있습니다. 

[root@osboxes ~]# docker load -i hello.tar
2edcec3590a4: Loading layer [==================================================>]  83.86MB/83.86MB
...
110.6kB/110.6kB
a9a1ab52037e: Loading layer [==================================================>]   7.68kB/7.68kB
Loaded image: hiondal/hello:1.0.0
[root@osboxes ~]# docker images
REPOSITORY      TAG       IMAGE ID       CREATED        SIZE
hiondal/hello   1.0.0     2981cd61b29c   16 hours ago   243MB



8) 더 이상 사용되지 않는 컨테이너와 이미지를 한꺼번에 정리할 수는 없을까 ?

docker container prune

docker image prune -a

 

'docker container prune' 명령은 중지된 모든 컨테이너를 한꺼번에 삭제해줍니다. 

아래 예와 같이 hello 컨테이너를 시작했다 중지한 후 'docker container prune'명령으로 삭제해보십시오. 

[root@osboxes ~]# docker run --name hello -d -p 8888:8080 hiondal/hello:1.0.0
aa22030d00ca25bf03c9e67f59ca83bfd42c16c30389b3eb9e646af4eede0670


[root@osboxes ~]# docker stop hello
hello


[root@osboxes ~]# docker ps -a --format "table {{.ID}}\t{{.Names}}\t{{.Status}}"
CONTAINER ID   NAMES     STATUS
aa22030d00ca   hello     Exited (137) 4 seconds ago


[root@osboxes ~]# docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
aa22030d00ca25bf03c9e67f59ca83bfd42c16c30389b3eb9e646af4eede0670

Total reclaimed space: 0B


[root@osboxes ~]# docker ps -a --format "table {{.ID}}\t{{.Names}}\t{{.Status}}"
CONTAINER ID   NAMES     STATUS

 

'docker image prune -a'명령은 더 이상 사용되지 않는 모든 이미지를 한꺼번에 삭제해줍니다. 

단 컨테이너가 사용중인 이미지는 삭제되지 않습니다. 

아래 예와 같이 이미지 'hiondal/hello:1.0.0'을 이 명령을 이용해 삭제해 보십시오. 

정상적으로 삭제 되려면 이 이미지를 이용해 실행 또는 정지된 컨테이너가 없어야 합니다. 

[root@osboxes ~]# docker images
REPOSITORY      TAG       IMAGE ID       CREATED        SIZE
hiondal/hello   1.0.0     2981cd61b29c   16 hours ago   243MB
[root@osboxes ~]# docker image prune -a
WARNING! This will remove all images without at least one container associated to them.
Are you sure you want to continue? [y/N] y
Deleted Images:
untagged: hiondal/hello:1.0.0
deleted: sha256:2981cd61b29c4d9f5b6696d9d4e15fca5810c71d42614f249ebc367f42804052
...
deleted: sha256:2edcec3590a4ec7f40cf0743c15d78fb39d8326bc029073b41ef9727da6c851f

Total reclaimed space: 243.2MB
[root@osboxes ~]# docker images
REPOSITORY   TAG       IMAGE ID   CREATED   SIZE
[root@osboxes ~]#

 

 

지금까지 제일 많이 사용하는 컨테이너 빌드 및 실행 툴인 도커의 핵심 내용을 배웠습니다. 

 

'Cloud > Docker' 카테고리의 다른 글

container image의 Layer구조 이해  (0) 2021.12.28
Docker 주요 명령  (1) 2020.11.15
docker 오프라인(offline) 설치 for centos, redhat(RHEL)  (3) 2020.10.15
ubuntu, centos를 container로 실행하기  (0) 2020.06.03
6. Beyond Docker  (0) 2019.09.12
댓글

전자책 출간 알림 [마이크로서비스패턴 쉽게 개발하기]