도커는 쿠버네티스의 기초 기술이다. 그러니까 쿠버네티스를 쓰려면 도커에 대해서 알아야 한다는 소리이다. 사실 쿠버네티스의 등장 자체가 컨테이너를 보다 수월하게 다루고 관리하기 위함이니 당연한 이야기이기도 하다.
도커(Docker)
도커는 가상실행 환경을 제공해주는 오픈소스 플랫폼이다. 도커는 이 가상실행 환경을 컨테이너라고 부르는데, 일반적으로 가상화라고 하면 가상머신을 많이 떠올리게 된다. 그만큼 도커와 기존 가상머신의 사용도나 개념이 비슷하다고 할 수 있다.
하지만 도커가 제공하는 가상실행환경인 컨테이너는 어플리케이션 단의 실행 환경을 가상화하는 것이 기존 가상머신과의 가장 큰 차이라고 할 수 있다. 가상머신은 하드웨어부터 어플리케이션 단계가지 가상화를 하는 것이기 때문에 리소스의 고립 단계에서는 컨테이너보다 깊다고 할 수 있다. 따라서 가상머신의 경우 서로 독립적으로 작동하는 환경을 구축할 때 컨테이너보다 유리하다고 할 수 있다.
컨테이너의 경우 하드웨어는 컨테이너를 사용할 수 있도록 만들어주는 도커 엔진이 실행되는 기존 운영체제를 통해 하드웨어 자원을 할당받아 보다 가볍고 빠른 속도로 작업을 수행할 수 있는 장점이 있다. 가상화를 시키는 대상도 가상머신에 비해 적기 때문에 그럴 수밖에 없을 것이다.
도커 설치
도커를 설치하는 방법은 간단하다. 리눅스 커널을 사용한다면 간단한 명령어를 통해 설치할 수 있다.
sudo apt update && sudo apt install -y docker.io net-tools
sudo usermod -aG docker $USER
sudo reboot
docker.io 패키지는 Debian에서 관리하는 패키지이다. 기능상 차이는 없고 설치 방식에서만 docker.com에서 설치하는 도커와 차이가 있다.
그런데 윈도우나 맥을 사용한다면 이 방법을 설치가 안된다. 근본적으로 도커는 리눅스 커널 위에서 작동하기 때문이다. 그래서 많이들 사용하는 것이 Docker Desktop과 같은 프로그램일 것이다. 사용하기도 쉽고 자료도 많아 편리하게 사용이 가능하다.
하지만 Docker Desktop이 엔터프라이즈 대상으로 유료로 전환되었기 때문에 이에 대한 대안으로 Rancher Desktop을 사용할 것이다. Rancher Desktop은 쿠버네티스를 관리할 수 있도록 도와주는 프로그램인데, 어차피 쿠버네티스가 도커 컨테이너를 잘 다루기 위해 만들어진 프로그램이기 때문에 기존 docker 명령어도 잘 돌아간다.
도커 기본 명령
도커에서 컨테이너를 실행하는 것은 우리가 평상시 하던 것처럼 프로그램 하나를 실행하는 것과 같은 의미를 가진다. 그래서 컨테이너를 실행하는 명령은 run이라는 명령어를 사용한다.
docker run <IMAGE>:<TAG> [<args>]
# cowsay 실행
docker run docker/whalesay cowsay 'hello kubectl!'
- docker/whalesay 우리가 실행하고 싶은 도커 이미지
- cowsay 'hello kubectl!' 실행할 도커 이미지에 전달할 인자
- 로컬에 docker/whalesay 이미지가 없으면 자동으로 이미지를 원격 저장소에서 가져와 설치
도커의 이미지 주소 형식은 다음과 같다.
<레지스트리 이름>/<이미지 이름>:<TAG>
docker/whalesay == docker.io/docker/whalesay:latest
레지스트리 이름은 도메인 주소 형식을 가진다. 생략한다면 기본적으로 docker.io 레지스트리를 사용하게 된다. 그리고 TAG를 생략하게 되면 latest가 기본적으로 사용된다.
백그라운드 실행
도커에서는 컨테이너를 백그라운드로 실행할 수도 있다. 그리고 이렇게 백그라운드로 실행을 하게 되면 컨테이너 ID가 리턴된다.
docker run -d nginx
897f33ae28e207d5ce931b7f34c59c6675dc08f204982ee1e3c2bcd3c5ebcb80
- 이미지 주소 : nginx == docker.io/nginx:latest
컨테이너 조회
리눅스에서 ps 명령어를 실행하면 현재 실행중인 프로세스를 확인할 수 있다. 컨테이너가 프로세스와 같으니 실행 중인 컨테이너를 조회하는 것 역시 동일하게 할 수 있다.
docker ps
컨테이너 상세정보 확인
조금 더 자세하게 컨테이너 정보를 확인하고 싶으면 이렇게 하면 된다.
docker inspect <CONTAINER_ID>
컨테이너 로깅
컨테이너에서 출력되는 로그도 기록할 수 있다.
docker logs <CONTAINER_ID>
그런데 우리는 로그를 모니터링 할 때 tail -f 명령어를 사용했는데, 도커에서도 동일하게 사용할 수 있다.
docker logs -f <CONTAINER_ID>
컨테이너 명령 전달
컨테이너는 하나의 독립적인 프로세스이다. 이렇게 실행된 프로세스에 새로운 명령을 내리고 싶을 수 있다. 새로운 패키지를 설치하거나 무언가 수정하거나 하는 작업 등 말이다. 이럴 때에는 exec 명령어를 사용하여 해당 컨테이너에 명령을 전달하면 된다.
docker exec <CONTAINER_ID> <CMD>
좀 전에 nginx라는 웹서버를 설치했으니 웹서버에 명령을 보내보자.
docker exec 897f33ae28e2 curl localhost
- docker ps로 확인한 nginx의 컨테이너 ID
- curl localhost를 실행하여 nginx에 질의
컨테이너 / 호스트 간 파일 복사
실행 중인 컨테이너와 호스트 사이에서 파일을 주고받을 수도 있다. 리눅스의 cp 명령어와 유사하다.
docker cp <HOST_PATH> <CONTAINER_ID>:<CONTAINER_PATH>
파일 복사를 위해 다시 한 번 nginx를 이용해보자. 도커를 이용해서 nginx를 설치하면 컨테이너 내부에는 /usr/share/nginx/html이라는 경로가 존재할 것이다. 이 경로는 nginx의 웹 서빙 디렉토리이다. 요청이 들어오면 해당 경로에 있는 파일을 리턴해주는 것이다.
docker cp ~/con.sh <CONTAINER_ID>:/usr/share/nginx/html/
# 이동된 파일을 nginx가 서빙하는지 확인
docker exec <CONTAINER_ID> curl localhost/con.sh
- 내 컴퓨터의 ~/con.sh를 도커 컨테이너로 이동
- exec 명령어를 사용해서 서빙이 되는지 확인
cp 명령어와 같이 src와 target의 위치를 변경하는 것으로 컨테이너에서 호스트로의 복사도 가능하다.
docker cp <CONTAINER_ID>:/usr/share/nginx/html/index.html ~/
컨테이너 중단
사용이 완료된 컨테이너를 중단한다.
docker stop <CONTAINER_ID>
중단을 하면 docker ps에서는 확인할 수 없지만 docker ps -a에서는 확인이 가능하다.
컨테이너 재개
중단한 컨테이너를 다시 실행할 수도 있다.
docker start <CONTAINER_ID>
이제는 docker ps에서 확인이 간으하다.
컨테이너 삭제
다 쓴 컨테이너를 삭제하는 방법이다.
docker rm <CONTAINER_ID>
이 경우에는 docker ps는 물론, docker ps -a에서도 확인이 불가능하다.
Interactive 컨테이너
컨테이너를 실행할 때 -it 옵션을 통해서 직접 컨테이너에 들어가 작업을 할 수 있다.
# 실행할 때 들어가기
docker run -it <IMAGE>:<TAG>
# 실행중인 컨테이너에 들어가기
docker exec -it <CONTAINER_ID>
컨테이너 내부로 들어가는 행위는 디버깅이나 임시로 패키지를 설치할 때 자주 사용한다.
컨테이너는 프로세스와 같다고 하였다. 그래서 컨테이너를 삭제하게 되면 컨테이너 내부의 파일 시스템에 저장되어 있는 패키지도 모두 삭제된다.
도커 저장소
도커 허브는 도커 이미지 원격 저장소이다. 사용자들이 도커 허브에 이미지를 업로드하고 다른 곳에서 자유롭게 사용할 수 있다. 깃허브랑 비슷한 개념이라고 보면 된다.
이미지 tag 달기
tag 명령을 이용하여 기존의 이미지에서 새로운 이름을 부여할 수 있다. nginx 이미지에 새로운 tag를 달아보자. USERNAME에는 도커 허브의 계정을 사용한다.
docker tag <OLD_NAME>:<TAG> <NEW_NAME>:<TAG>
# nginx 이미지에 태그 달기
docker tag nginx:latest <USERNAME>/nginx:1
# <TAG> 생략 버전
docker tag nginx <USERNAME>/nginx
이미지 확인
원격 저장소에서 다운로드한 도커 이미지 리스트를 확인한다.
docker images
여기에서 우리가 설정한 tag를 가진 이미지도 확인할 수 있다. 근데 같은 IMAGE_ID를 가지고 있다면 이름만 다르고 사실상 같은 이미지인 것이다.
도커 허브 로그인
도커 허브에 로그인하여 이미지를 업로드할 수 있다.
docker login
로그인할 때 Error saving credentials: error storing credentials - err: exec: "docker-credential-desktop": executable file not found in $PATH, out: 이런 문구가 뜨며 안되면 ~/.docker/config.json 파일에서 "credsStore" : "desktop" 이걸 지워보자.
이미지 업로드
도커 이미지를 도커 허브에 업로드하는 것 역시 간단한다. 깃허브에 푸시하는 것과 같다.
docker push <USERNMAE>/<NAME>
# nginx 이미지 업로드
docker push <USERNAME>/nginx
이미지 다운로드
깃허브에서 새롭게 업데이트된 코드들을 받을 때 pull을 썼다. 도커도 동일하다.
docker pull <IMAGE_NAME>
# redis 이미지 받기
docker pull redis
run을 사용하면 이미지가 로컬에 없을 경우 다운로드하여 설치하고 동시에 실행을 했는데, pull을 사용하면 명시적으로 설치만 하게 된다. 설치가 완료되면 docker images 명령어로 이미지를 확인할 수 있다.
이미지 삭제
로컬에 있는 이미지를 삭제한다.
docker rmi <IMAGE_NAME>
# nginx 삭제
docker rmi <USERNAME>/nginx
도커 파일 작성
지금까지는 다른 사용자가 만든 도커 이미지를 이용하여 컨테이너를 실행했다. 이번에는 직접 나만의 도커 이미지를 만들어볼 것이다. 도커 이미지를 생성하기 위해서는 Dockerfile이라는 텍스트 문서를 작성해야 한다. 사용자는 Dockerfile에 특정 명령을 기술하여 원하는 도커 이미지를 생성할 수 있다. 특정 명령은 다음과 같다.
- Dockerfile에 기반 이미지(base image)를 지정
- 원하는 소프트웨어 및 라이브러리를 설치하기 위한 명령을 기술
- 컨테이너 실행 시 수행할 명령을 기술하는 것
Dockerfile 기초
먼저, 간단한 파이썬 스크립트와 도커 파일을 만들어보자.
# hello.py
import os
import sys
my_ver = os.environ["my_ver"]
arg = sys.argv[1]
print("hello %s, my version is %s!" %(arg, my_ver))
# Dockerfile
FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y \
curl \
python-dev
WORKDIR /root
COPY hello.py .
ENV my_ver 1.0
CMD ["python", "hello.py", "guest"]
- FROM은 기반 이미지를 나타낸다. 해당 이미지를 기반으로 새로운 도커 이미지가 생성된다.
- RUN에서는 사용자가 지정한 명령을 실행하게 된다.
- WORKDIR은 이미지의 작업 폴더를 지정하는 명령어이다. 여기에서는 /root 디렉토리를 사용한다.
- COPY는 로컬에 있는 파일을 이미지 안으로 복사할 때 사용한다.
- ENV는 이미지의 환경변수를 지정한다.
- CMD는 이미지를 실행할 때, 디폴트로 실행되는 명령을 지정한다.
도커 빌드
도커 빌드는 작성한 Dockerfile을 도커 이미지로 변환하기 위한 빌드 명령이다.
docker build <PATH> -t <IMAGE_NAME>:<TAG>
- <PATH>는 Docker 파일이 위치한 경로를 나타낸다.
- <IMAGE_NAME>과 <TAG>는 만들고 싶은 이미지의 이름과 tag를 나타낸다.
빌드가 완료되면 docker images를 통해 우리가 만든 도커 이미지를 확인할 수 있다.
이제 빌드한 도커 이미지를 실행해보자. 위에서 작성한 파이썬 스크립트가 입력되는 인자에 따라 출력되는 값이 달라지도록 설정했기 때문에 이에 유의하면서 살펴보면 된다.
docker run hello:1
# hello guest, my version is 1.0!
docker run hello:1 echo "Hello World"
# Hello World
# 실행하면서 매개변수를 넘기면 Dockerfile에 작성한 CMD는 무시된다.
docker run hello:1 cat hello.py
# import os
# import sys
# ...
docker run hello:1 pwd
# /root
# WORKDIR에 설정한 경로
컨테이너를 실행할 때 -e 옵션을 사용하면 환경변수를 주입할 수 있다.
docker run -e KEY=VALUE <REGISTRY>/<IMAGE>:<TAG>
my_env를 1.5로 수정해서 실행하면 다음과 같다.
docker run -e my_ver=1.5 hello:1
# hello guest, my version is 1.5!
Dockerfile 심화
ARG
Dockerfile 안에서 사용할 수 있는 매개변수를 ARG라고 한다. 파라미터로 넘겨지는 변수의 값에 따라, 생성되는 이미지 내용을 변경할 수 있다.
# Dockerfile
FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y \
curl \
python-dev
ARG my_ver=1.0
WORKDIR /root
COPY hello.py .
ENV my_ver $my_ver
CMD ["python", "hello.py", "guest"]
좀 전에 작성한 Dockerfile에서 ARG를 사용하여 my_ver이라는 변수를 사용했다. 기본으로 1.0으로 작성되는데, 이미지를 빌드할 때 --build-arg 옵션을 사용하여 ARG 값을 덮을 수 있다. 덮는 방법은 다음과 같다.
docker build . -t hello:2 --buiild-arg my_ver=2.0
docker run hello:2
# hello guest, my version is 2.0!
물론 이미지 빌드를 할 때 설정한 my_ver ARG 역시 실행을 하면서 환경변수 변경이 가능하다.
docker run -e my_ver=3.0 hello:2
# hello guest, my version is 3.0!
ENTRYPOINT
CMD는 이미지가 실행될 때 실행되는 명령어들이다. ENTRYPOINT는 이와 유사하나 실행 명령이 덮어지지 않는 특징을 가진다.
# Dockerfile
FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y \
curl \
python-dev
WORKDIR /root
COPY hello.py .
ENV my_ver 1.0
ENTRYPOINT ["python", "hello.py", "guest"]
이렇게 만든 Dockerfile을 빌드해보자.
docker build . -t hello:3
docker run hello:3
# hello guest, my version is 1.0!
docker run hello:3 echo "Hello World"
# hello guest, my version is 1.0!
# 명령어 인자를 주어도 변하지 않음
echo 명령어를 인자로 전달해도 실행 명령이 덮어지지 않는다. 이는 ENTRYPOINT를 사용하면 이미지 자체가 실행 가능한 바이너리처럼 바뀌기 때문이다. 대신에 ENTRYPOINT를 사용하면 파라미터 전달 시, 해당 값이 ENTRYPOINT의 파라미터로 그대로 전달된다.
# Dockerfile
FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y \
curl \
python-dev
WORKDIR /root
COPY hello.py .
ENV my_ver 1.0
ENTRYPOINT ["python", "hello.py"]
ENTRYPOINT를 이렇게 변경하자. 이렇게 하면 매개변수로 전달하는 인자들이 ENTRYPOINT의 파라미터로 전달될 것이다.
docker build . -t hello:4
docker run hello:4
# File "hello.py", line 6, in <module>
# arg = sys.argv[1]
# IndexError: list index out of range
docker run hello:4 world
# hello world, my version is 1.0!
마치 python hello.py world처럼 실행된다.
도커 실행 고급
Network
docker run -p <HOST_PORT>:<CONTAINER_PORT> <IMAGE_NAME>
외부의(호스트) 트래픽을 컨테이너 내부로 전달할 수 있다. 이를 위해 호스트의 포트와 컨테이너의 포트를 매핑시켜 트래픽을 포워딩하게 된다. 다음은 호스트의 5000번 포트에 대한 요청을 컨테이너 내부의 80번 포트와 매핑하여 트래픽을 포워딩하는 예제이다.
현재 포트가 어디 어디 사용 중인지 확인하고 포트를 매핑하자
sudo lsof -PiTCP -sTCP:LISTEN
docker run -p 12500:80 -d nginx
curl http://localhost:12500
Volume
docker run -v <HOST_DIR>:<CONTAINER_DIR> <IMAGE_NAME>
도커 컨테이너는 프로세스이다. 이 말은 컨테이너가 중지되면 그 안에 있는 데이터가 사라진다는 소리이다. 그래서 컨테이너 내부의 데이터를 영구적으로 저장하기 위해 볼륨을 사용할 수 있다. 볼륨은 컨테이너를 실행할 때, 로컬의 파일 시스템과 컨테이너를 연결하여 마운트를 하게 되는 것이다.
# 현재 디렉토리를 컨테이너의 nginx 디렉토리와 연결(마운트)
docker run -p 12500:80 -v $(pwd):/usr/share/nginx/html -d nginx
# 현재 디렉토리에 hello.txt 파일 생성
echo hello! >> $(pwd)/hello.txt
# 컨테이너에 hello.txt가 생겼는지 확인
curl http://localhost:12500/hello.txt
# hello!
변경사항이 많은 파일의 경우, 컨테이너 내부에 파일을 두지 않고 호스트의 로컬 디렉토리에 연결하면 보다 쉽게 파일을 수정할 수 있다. 또한, 컨테이너는 종료 시 저장된 데이터가 사라지지만 볼륨을 사용한다면 영구적으로 데이터를 유지할 수 있다.
Entrypoint
앞서 Dockerfile을 만들 때 ENTRYPOINT를 사용하면 덮을 수 없는 명령어로 사용할 수 있다고 했지만 --entrypoint 옵션을 사용하면 강제로 덮어버릴 수 있다.
# Dockerfile
FROM ubuntu:18.04
ENTRYPOINT ["echo"]
docker build . -t lets-echo
docker run lets-echo hello
# hello
docker run lets-echo cat /etc/passwd
# cat /etc/passwd
docker run --entrypoint=cat lets-echo /etc/passwd
# root:x:0:0:root:/root:/bin/bash
# daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
# bin:x:2:2:bin:/bin:/usr/sbin/nologin
# sys:x:3:3:sys:/dev:/usr/sbin/nologin
# sync:x:4:65534:sync:/bin:/bin/sync
# ...
User
도커 컨테이너의 유저는 root이다. 보안상의 이유로 일반 유저를 사용하는 것도 가능하다.
# Dockerfile
FROM ubuntu:18.04
# Ubuntu 유저 생성
RUN adduser --disabled-password --gecos "" ubuntu
USER ubuntu
docker build . -t my-user
# ubuntu 유저로 컨테이너 실행
docker run -it my-user bash
ubuntu@1673683933fb:/$
apt update
Reading package lists... Done
E: List directory /var/lib/apt/lists/partial is missing. - Acquire (13: Permission denied)
ubuntu@1673683933fb:/$
# 강제 root 실행
docker run --user root -it my-user bash
root@8e6d5ef01495:/#
apt update
Get:1 http://ports.ubuntu.com/ubuntu-ports bionic InRelease [242 kB]
Get:2 http://ports.ubuntu.com/ubuntu-ports bionic-updates InRelease [88.7 kB]
Get:3 http://ports.ubuntu.com/ubuntu-ports bionic-backports InRelease [74.6 kB]
Get:4 http://ports.ubuntu.com/ubuntu-ports bionic-security InRelease [88.7 kB]
Get:5 http://ports.ubuntu.com/ubuntu-ports bionic/universe arm64 Packages [11.0 MB]
...
CLEAN UP
쿠버네티스의 기초가 되는 도커와 컨테이너에 대해 알아보았다. 컨테이너는 도커가 설치된 어디에서든지 정상적으로 동작할 수 있는 발판을 마련해준다. 쿠버네티스는 이러한 도커의 특징을 잘 이용하여 단순히 하나의 서버에서뿐만 아니라, 여러 서버에서 여러 개의 컨테이너를 어떻게 잘 실행시킬 것인지에 집중하고 있다.
# 생성된 모든 컨테이너 삭제
docker rm $(docker ps -aq) -f
docker rmi $(docker images -q) -f
'쿠버네티스' 카테고리의 다른 글
쿠버네티스 네트워킹(Service 리소스) (0) | 2022.07.13 |
---|---|
Pod 살펴보기 (0) | 2022.06.30 |
쿠버네티스 기본 명령어 (0) | 2022.06.26 |
쿠버네티스 설치 (0) | 2022.06.24 |
쿠버네티스 소개 (0) | 2022.06.07 |