기록 블로그

프로그래머스 데브코스-데이터 엔지니어/TIL(Today I Learned)

06/12 46일차 개발환경 구축을 위한 Docker & K8s (1)

usiohc 2023. 6. 12. 23:32

학습 주제


주요 메모 사항


먼저 Airflow 운영상의 어려움을 설명

관리해야하는 DAG의 수가 100개를 넘어간다면?

데이터 품질이나 데이터 리니지 이슈 이외에도 다양한 이슈들이 발생

어떤 이슈들이 있을까?

- 라이브러리 충돌

- Worker의 부족

- Worker 서버들의 관리와 활용도 이슈

 

 

1. 라이브러리 충돌

라이브러리/모듈의 충돌 이슈가 발생하기 시작함

DAG에 따라 실행에 필요한 라이브러리/모듈이 달라지기 시작

 - 예) Python 버전

이로 인해 DAG 혹은 Task별로 별도의 독립공간을 만들어주는 것이 필요

 - Docker to the rescue

 - Dag 혹은 Task 코드를 Docker Image로 만들고 이를 독립된 공간(Docker Container)안에서 실행

 

 

2. Worker의 부족, upgrade 순서

Scale Up

Scale Out - 가능하면 클라우드 서비스 사용 

K8s와 같은 컨테이너 기술 사용 -필요한대로 서버 요청

 

3. 낮은 Server Utilzation 이슈

Airflow 전용 하드웨어를 지정했는데 서버들이 항상 바쁘지 않다면? Peak time

- 만약 10대의 서버에 Airflow를 할당했다면?

서비스별로 전용 서버를 할당하는 것은 여러가지로 이슈를 만들어냄

- 서비스별로 Capacity 관리를 해야함

- 각 서비스에 속한 서버들은 보면 utilization이 낮은 이슈 발생

이 역시 K8s와 같은 컨테이너 기술의 도입으로 해결 가능


해결책

태스크나 DAG 코드를 Docker Image로 만들어서 Docker Container 형태로 실행

- 라이브러리/모듈 충돌을 방지

- 개발 환경과 프로덕션 환경을 동일하게 유지

 

Airflow Worker를 K8s에서 필요한 대로 동적으로 할당하여 사용

- 전용 서버를 Airflow에 할당하지 않고 Container Orchestration 서비스를 통해 할당해서 사용하고 리턴

 

 

Airflow에서 이를 해결하는 방법은 3가지

a. Airflow Operator로 KubernetesPodOperator를 사용

b. Airflow Operator로 DockerOperator를 사용

c. Airflow Executor로 아래를 사용

    ■ KubernetesExecutor
    ■ CeleryKubernetesExecutor
    ■ LocalKubernetesExecutor

 

-> Airflow Executor이란?

Executor는 Task들을 관리하고 실행하는 역할을 수행

- 병렬 혹은 일렬 실행이나 어느 worker에서 실행할지 등등

 

다양한 수의 Executor 타입이 존재

Sequential Executor: 디폴트로 설치되며 Sqlite와 같은 싱글스레드 DB에서만 사용가능 worker node가 1개 == master node도 1개

Local Executor: task들을 Airflow 마스터 노드안에서 실행

Celery Executor: 다수의 Worker 노드가 있는 경우 사용되며 Celery 큐를 사용해 task들을 worker 노드로 분산하여 실행

Kubernetes Executor는 K8s 클러스터를 사용하여 task들을 독립된 환경에서 사용

Local Kubernetes Executor와 Celery Kubernetes Executor도 존재

 

 

Airflow 아키텍처 : Docker와 K8s를 사용하는 방법

Airflow Operator로 KubernetesPodOperator를 사용

- 이 방식은 특정 태스크를 Docker Image로 만들어 K8s에서 실행

 

Airflow Operator로 DockerOperator를 사용

- 이 방식은 특정 태스크를 Docker Image로 만들어 Docker Container 위 에서 실행

 

Airflow Executor로 다음 중의 하나를 사용

- KubernetesExecutor - 모든 DAG 코드가 Docker Image로 빌드되어 K8s에서 실행됨

- CeleryKubernetesExecutor

    - CeleryExecutor와 KubernetesExecutor를 동시에 사용하는 방법을 제공해주는 Executor

    - 이는 Airflow 로드가 전체적으로 큰데 소수의 task만 Isolation을 필요로 하는 경우

- LocalKubernetesExecutor - LocalExecutor와 KubernetesExecutor를 동시에 사용하는 방법을 제공해주는 Executor

 

 


Docker 소개

내가 만든 프로그램이 다른 컴퓨터에서 안 돌아간다면?

설치 과정에서 중요한 파일이 빠짐

사용하는 라이브러리 등의 버전이 안 맞음 -> 가장 골치 아픈 문제!

환경 설정이 안 맞는 것이 존재

 

-> 내 컴퓨터 환경을 그대로 패키징해서 다른 이에게 줄 수 있다면?

Docker Image: 이렇게 독립적으로 완전하게 만들어진 패키지

Docker Container: 이 Docker Image를 독립된 환경에서 실행한 것

 

-> Docker의 목표, 소프트웨어를 일관되게 빌드하고 실행하고 배포

그래서 아래와 같은 이미지의 간단한 관리가 가능한 것임

 


그렇다면 Virtual Machines vs. Docker Containers ?

 

 

 

Virtual Machine 소개

AWS의 EC2가 대표적인 Virtual Machine, GCP의 Compute Engine 

하드웨어를 추상화하여 한 컴퓨터 위에 가상 컴퓨터를 올리는 것

 

필자 생각 : WSL이 윈도우11에서 정말 편함

 

장점

소프트웨어를 실행하기 위한 독립적이고 분리된 공간을 제공

다수의 소프트웨어를 각 VM단에서 독립적으로 실행가능

 

 

단점

각 VM은 자신만의 OS를 필요로 함 (가상 하드웨어위에서 돌기 때문)

- 유료 OS라면 라이센스 비용 필요

- 그러다보니 시작하는데 오래 걸림

자원을 많이 사용함 (VM들끼리 자원을 나눠써야함)

 

Docker가 나오기 이전에는 VM을 많이 사용했음, 

 

 

Docker Container 소개

Container 이전에 Docker Engine는 Linux 기술을 사용해 개발되었음

Docker Engine은 기본적으로 Linux 컨테이너를 지원

Linux 컨테이너는 호스트 운영 체제의 커널을 공유하여 프로세스를 격리된 환경으로 실행하는 가벼운 가상화 기술

소프트웨어를 실행하기 위한 독립적이고 분리된 공간

자체 파일 시스템을 갖고 있음 (Volume이라고 부름)

오른쪽 이미지는 지원하는 컨테이너 OS를 설명하는 것

Host OS 지원하는 컨테이너 OS
맥  경량화된 리눅스 VM이 동작함
윈도우 윈도우, 리눅스
리눅스 리눅스

 

 

장점

소프트웨어를 실행하기 위한 독립적이고 분리된 공간을 제공

- 다수의 소프트웨어를 각 컨테이너단에서 독립적으로 실행가능

 

자원 소비가 적음 (lightweight)

- 몇 십개에서 몇 백개의 container를 실행 가능

 

호스트 OS를 사용 (별도 비용 없음)

- 따라서 빠르게 실행됨

 

 

단점

많은 수의 Docker Container를 관리하는 것은 쉽지 않음

Host OS를 사용하기에 Cross-platform compatibility를 항상 지원하지 않음

GUI 소프트웨어 개발에 적합치 않음

 

 

단점 1번에 많은 수의 Docker Container를 관리하는 것은 쉽지 않음이 K8s에서 해결되는 것

 

 


Docker 설치

필자는 Docker for Window가 설치되어 있으나, wsl ubuntu 위에 Docker을 하나 더 설치했음


Docker 프로그램 개발 프로세스

하이레벨 Docker 사용 프로세스

먼저 대상 소프트웨어를 선택

- 다수의 컴포넌트로 구성되는 소프트웨어라면 각각이 Docker Image로 만들어져야할 수도 있음

 

ㄴ> 이를 Docker Image로 빌드하자: Dockerization이라고 부름

    - Dockerfile이란 텍스트 파일로 세부 정보를 기술

        - 해당 소프트웨어를 이미지로 바꾸기 위한 Docker에게 주는 명령들을 포함

    - Docker Image: 하나의 Docker Container안에서 실행됨!

        - Dockerfile을 기준으로 만들어지며 소프트웨어를 실행하기위해 필요한 모든 것을 포함

 

 

Docker Image의 구성 요소

- 기본 OS (리눅스라면 우분투, 데비안 등등)와 같은 소프트웨어의 실행환경

- 소프트웨어 자체 (코드)

- 소프트웨어가 필요로 하는 라이브러리

- 파일 시스템 스냅샷: 이는 스택화된 형태로 구현됨 (뒤에서 더 설명)

- 환경 설정 변수: 빌드할 때 변수와 실행 때 변수 두 가지가 존재

- 메타 데이터: 이미지 자체에 대한 정보 (버전, 작성자, 설명 등등)

 

-> 위 정보와 설치 관련 실행 순서등이 Dockerfile에 기술됨

    - Docker Image는 다수의 파일로 구성됨 (“docker image ls”) 

 

 

 

Docker Image의 실행

Container를 통해 Docker Image안의 소프트웨어를 실행

- Container는 자체 파일 시스템을 가진 특수한 프로세스로 이미지의 파일 시스템이 로딩됨

 

Image를 Container 안에서 실행

방법 : docker run, docker exec

-> 두개의 차이 run은 처음 실행하는 image를 컨테이너 안으로 띄우는거 exec는 이미 실행된 docker에 명령을 내리는 것임

 

 

Docker Image의 등록: Docker Hub

Docker Registry는 Docker Image들의 보관소

- On-prem registry와 Cloud registry가 존재

- docker hub 가장 유명

여기에 등록을 하면 회사내 혹은 퍼블릭하게 이미지를 공유 가능

 

 

 

Docker Hub이란 무엇인가?

Docker가 제공해주는 서비스로 Docker Image를 공유하고 찾기 위한 서비스

Git과 거의 유사한 거라고 보면 될 듯

 

 


실습 1: 간단한 Hello World

만들려는 프로그램 개요

Node.js로 구성된 초간단 웹 서비스, app.js가 전부!

- 하지만 node 런타임 환경이 필요, 보통 이를 실행하려면 `$ node app.js`

node 환경이 설정되어 있어야함!

 

 

만들려는 프로그램 실행 (Docker 없이)

---------------------------------------------------------

직접 설치/실행시 순서

a. OS 선택

b. Node 설치

c. 코드 복사

d. 프로그램 실행 (node app.js)

---------------------------------------------------------

 

위 내용을 Dockerfile에 기술하면 Docker Image를 생성하는 것임

 

 

Dockerfile의 생성

Docker에게 소프트웨어 설치 명령을 기술

먼저 베이스 이미지를 기술 (FROM)

다음으로 코드 복사 (COPY)

마지막으로 코드 실행 (CMD)

 

 

Dockerfile 사용 가능 기타 키워드

ARG : Docker Image를 만들 때 사용되는 변수 지정. 최종 이미지에는 안 들어감

ENV : 컨테이너가 실행될 때 사용되는 환경변수. 최종 이미지에 저장됨

USER : 컨테이너를 실행할 때 사용할 유저 ID

EXPOSE : 서비스 사용 포트번호

RUN : 빌드시 실행되어야하는 명령들이 지정됨 (docker build)

            `$ RUN apt-get update && apt-get install -y cur`

 

 

 

 

Dockerfile 키워드: CMD vs. ENTRYPOINT

Container가 시작할 때 실행되어야 하는 명령어를 지정하는데 사용 (docker run ~~~)

- 굉장히 흡사한 기능을 제공하지만 우선 순위가 있음

 

둘다 한 DOCKERFILE에서 여러번 실행되면 각각 마지막 것만 사용됨!

- 아래의 경우 docker run 실행시 동일한 결과가 나옴 -> command2.sh만 실행

 

 

-> CMD나 ENTRYPOINT 중 하나만 지정되면 그게 container가 실행될 때 실행

오른쪽 이미지에서 `$ docker run my-image ~~~.sh`를 사용하면 지정한 스크립트를 실행시킬 수 있다는 것

 

 

-> 둘이 한 DOCKERFILE에서 같이 지정가능함

둘이 같이 사용되면 ENTRYPOINT가 기본 명령이 되고 CMD가 인자를 제공

즉, entrypoint.sh 매개변수1 매개변수2 과 같은 명령어가 전달되는 것임!!

ENTRYPOINT는 --entrypoint 옵션을 통해서만 덮어쓰기가 가능

 

 

 

그렇다면 CMD or ENTRYPOINT?

최대한 CMD만 사용

ENTRYPOINT를 사용하면 실행시 타이핑을 덜 할 수 있음

 - 파라미터를 지정해주면 되기 때문이지만 감춰지기 때문에 오히려 혼란을 줄 수 있음

 

--- dockerfile ---

ENTRYPOINT ["python"]
CMD ["app.py"]

------ bash ------

python app.py

---------------------

처럼 실행된다는 것이지만 우리는 CMD만 사용할 예정!!

요약

1. ENTRYPOINT가 있으면 CMD 값이 파라미터로 실행됨

2. 아니면 CMD가 실행됨

 

 

Dockerfile 더 살펴보기

Dockerfile :  Airflow 예시

 
FROM python:3.7-slim-buster
ENV DEBIAN_FRONTEND noninteractive # ENV 는 컨테이너 환경변수, 이미지에도 최종 저장된다고 기억
ARG AIRFLOW_USER_HOME=/usr/local/airflow # ARG 는 빌드 환경변수임
ARG AIRFLOW_VERSION=1.10.9
COPY config/airflow.cfg ${AIRFLOW_USER_HOME}/airflow.cfg
RUN chown -R airflow: ${AIRFLOW_USER_HOME} # 이미지를 빌드할 때 실행되는 명령들
EXPOSE 8080 5555 8793 # 웹서버 포트 여는 작업
USER airflow
WORKDIR ${AIRFLOW_USER_HOME}
 
# /entrypoint.sh webserver 명령어 실행되는거임!! 위에서 설명한 python app.py랑 똑같음
ENTRYPOINT ["/entrypoint.sh"]
CMD ["webserver"]

 

 

Docker Image 생성

$ docker build --platform linux/amd64 -t hello-world-docker .

=> [internal] load build definition from Dockerfile

=> => writing image

sha256:cb6c638168780afd3d74fc1cddd813917a6a397dad453c8e1a8063635c1521fe 0.0s

 

=> => naming to docker.io/library/hello-world-docker

Docker build를 실행하면 Dockerfile에서 Run 명령이 실행됨

 

--platform linux/amd64 명령

만일 Apple M1 chip 기반 맥에서 빌드하는 경우 그 이미지는 ARM 기반 아키텍처로 만들어지기 때문에 일반 리눅스에서 안 돌아감. 그래서 --platform 옵션을 사용해서 linux/amd64로 지정 -> CPU 아키텍처에 영향을 받는다. 이 부분이 조금 이해가 안감

 

-t hello-world-docker .

-t 태그는 뒤에서 따로 다룰 예정!!, 그리고 맨마지막에 comma . 가 있는데 이는 dockerfile의 PATH를 뜻하는 것임

 

 

 

Docker Image 확인

$ docker image ls

REPOSITORY TAG IMAGE ID CREATED SIZE

hello-world-docker latest cb6c63816878 22 minutes ago 179MB

TAG의 값에 아무것도 지정하지 않았기 때문에  lastest로 지정된 것임

 

Docker Container로 실행

$ docker run hello-world-docker

Hello Docker!

docker run을 Dockerfile에서 CMD 명령이 실행됨

 

만일 이 이미지를 다른 컴퓨터에서 실행하고자 한다면?

-> Docker Registry (예를 들면 Docker hub)으로 먼저 등록

 

 


Docker : hello-world-docker repo 만들기

1) 회원가입후 Create repository 선택, repo 이름 지정

 

2) 아까전에 만들었던 app.js를 dockerfile로 등록하고 해당 image를 docker repo에 upload 할 것

 
$ docker image ls
$ docker tag hello-world-docker:latest Username/hello-world-docker:latest
$ docker image ls
$ docker login --username=Username
$ docker push Username/hello-world-docker
 

 

 

Docker에 로그인하고 password를 입력하면 된다.

근데 경고 메세지를 보면 알겠지만 내가 서버에 접속한 계정의 .docker/config.json에 내 패스워드가 저장된다 라고 알려주는데 이를 credential hepler를 사용해서 비밀번호를 암호화 해야하는 것 같다. 아마도 이번 강의에서 다루지는 않을 듯?

 

 

Mounted from libaray/node 에서 자동으로 node 라이브러리를 이미지에 import 하는 것 같다. 

 

 

Push까지 완료 했으면 다시 Docker Web 에서 확인해보자

해당 과정까지 진행했으면 실습은 끝났다. 이제야 도커를 왜 쓰는지 이유를 알겠다!

 

 


Docker Hub로부터 받은 Image 실행

다른 서버에서 이미지를 다운로드 받아 컨테이너로 실행하기

- https://labs.play-with-docker.com/를 사용해서 4시간동안 서버를 하나 무료로 사용

- Docker Hub에 로그인이 되어있다면 그냥 계정 연결을 허용하면 됨

 

왼쪽에 new instance를 클릭, (Memory를 4기가나 준다??)

 

 

터미널 윈도우를 Option+Enter(윈도우는 Alt+Enter)로 최대화하고 아래 명령 실행


$ docker version
$ docker pull username/hello-world-docker
$ docker image ls
$ docker run username/hello-world-docker
 

 

 

Docker Desktop에서 로그인

(불필요하지만) Docker Desktop에서 Docker Hub으로 로그인해두면 Desktop에서 Pull, Push 등을 수행 가능 하다는 것만 알아두자, 필자는 desktop으로 진행 안했

 

 

여기서 사용해본 Docker 명령 요약


$ docker version
$ docker build -t
$ docker push
$ docker login
$ docker tag
$ docker pull
$ docker run
    p option (port mapping)
    v option (volume mapping)

-> 한번씩 직접 해보면 금방 느낌이 온다 git 이랑 거의 비슷하다고 생각함

 

 

docker run vs. docker exec

docker run과 docker exec의 차이점은 무엇일까?

- docker run은 새로 Container를 실행하는 것

- docker exec는 실행된 Container에 작업을 하는 것 - 그래서 이 명령은 Container ID가 필요함

 - 두 명령 모두 --user root 혹은 -u root를 통해 루트 유저로 연결가능

 

 

 

Docker Image 이름

docker image ls와 docker images와 동일한 결과를 보여줌

앞서 docker tag 명령의 경우 별칭을 만들어주는 것임 (이름을 바꾸는 것이 아님)

- $ docker tag hello-world-docker usiohc/hello-world-docker

 

아까전에 docker image를 생성하고 push하고 pull했을때, Image ID 가 같다는 것을 알 수 있음, Docker image의 실제 ID는 IMAGE ID임

Image 이름 자체는 REPOSITORY 이름과 TAG로 구성됨, 따로 docker에서 말하는 규칙은 없는 것 같다고 하심

-> 한번에 쓰는 경우 :을 사이에 두고 같이 씀 -> redis:13, keeyong/hello-world-docker:latest

 

docker image ls에서 Repository에 해당

- Docker Hub에서 다운로드받은 것이라면 어카운트ID(네임스페이스)를 포함할 수 있음

- 하지만 공식이미지는 네임스페이스가 없음

 

포맷: Repo이름:태그

- 예: keeyong/hello-world-docker:latest

- 예: bitnami/airflow

- 예: hello-world-docker:latest

공식 이미지들의 경우에는 네임스페이스가 없음

- 예: ubuntu:18.04

- 예: node:alpine

 

 

Docker tag란?

Docker Image의 버전이나 변형을 나타내는 문자열

- 디폴트 값은 latest

- Docker Image의 부가정보를 나타냄

 

Docker Image 이름에서 :뒤에 해당

- 예: ubuntu:18.04

- 예: bitnami/airflow

- 예: node:alpine

 


실습 2:우분투

Docker 위에서 ubuntu를 컨테이너로 실행할 수 있다!

필자는 진행 안함


$ docker run ubuntu
$ docker ps
$ docker ps -a
$ docker run -it ubuntu

실습 3: MySQL

MySQL 8.0을 Docker로 실행, 필자는 강의만 보고 진행은 안했음.

먼저 Docker Engine이 실행된 것 확인하고 terminal 프로그램 실행

MySQL docker image를 다운로드

$ docker pull mysql/mysql-server:8.0

 

다운로드받은 이미지로 Docker container 실행

$ docker run --name=mysql_container mysql/mysql-server:8.0

 

MySQL root 계정의 패스워드 찾기

$ docker logs mysql_container 2>&1 | grep GENERATED

 

마지막으로 MySQL shell 실행하기

$ docker exec -it mysql_container mysql -uroot -p

 

 


공부하며 어려웠던 내용

강의에서 다루지 않았던 디테일한 내용들이 궁금해졌다. (본문에 조금씩 적어놨음)