[DEV] 11주차. Docker & K8s (1)
1. Airflow 운영 상의 어려움
- 관리해야 하는 DAG의 수가 100개를 넘어간다면
- 데이터 품질이나 데이터 리니지 이슈 외에도 다양한 이슈들이 발생
- 라이브러리 충돌
- Worker의 부족
- Worker 서버들의 관리와 활용도 이슈
- 노드들이 많아질 때, 모든 노드들이 항상 바쁜 것은 아니기 때문에 놀고있는 CPU, Disk, Memory 등이 생길 것
라이브러리 충돌
- DAG마다 다른 종류의 SW / 같은 종류의 SW이지만 버전이 다른 경우 / dependencies가 있는 라이브러리들의 버전이 다른 경우 들 라이브러리/모듈의 충돌 이슈가 발생하기 시작
- DAG에 따라 실행에 필요한 라이브러리/모듈이 달라지기 시작
- Python 버전 등
- 이로 인해 DAG 혹은 Task 별로 별도의 독립 공간을 만들어주는 것이 필요
- Docker
- DAG 혹은 Task 코드를 Docker Image로 만들고, 이를 독립된 공간 (Docker Container)에서 실행
Worker의 부족
- Scale Up
- 서버 사양 높임
- 어느 시점에는 한계에 도달
- Scale Out
- Worker 노드를 Master와 별개의 서버로 분리 후 Worker 노드 증가시킴
- 클라우드 서비스 이용
- K8s와 같은 컨테이너 기술 사용
- 필요한대로 서버 요청
- K8s: 언제든지 다양한 용도로 사용될 수 있는 공용 서버 클러스터
낮은 Server Utilization 이슈
- Airflow 전용 하드웨어를 지정했는데, 서버들이 항상 바쁘지 않다면?
- 서비스 별로 전용 서버를 할당하는 것은 여러가지로 이슈를 만들어냄
- 서비스 별로 Capacity 관리를 해야 함
- 이 역시 K8s와 같은 컨테이너 기술의 도입으로 해결 가능
해결책
- Task나 DAG 코드를 Docker Image로 만들어서 Docker Container 형태로 실행
- 라이브러리 / 모듈 충돌을 방지
- 개발 환경과 프로덕션 환경을 동일하게 유지
- Airflow Worker를 K8s에서 필요한 대로 동적으로 할당하여 사용
- 전용 서버를 Airflow에 할당하지 않고, Container Orchestration 서비스를 통해 할당해서 사용하고 리턴
- Airflow에서 이를 해결하는 방법
- Task를 구현하는 것이 Operator
- 기존의 다른 Operator들도 사용할 수 있음
- Airflow Operator로
KubernetesPodOperator
사용- Task 자체를 Docker Image로 만들어서 전용 Worker 노드가 아닌 Kubernetes 서버에서 실행시키는 방법
- Airflow Operator로
DockerOperator
사용- 실행되는 것이 Docker Image이어야 함
- Docker Cotainer를 Airflow Worker 위에서 론치한다는 것!
- Airflow Executor로 아래를 사용
- Kubernetes HW를 받아서 사용하는 것 (K8s만 / 하이브리드 형태로)
KubernetesExecutor
CeleryKubernetesExecutor
LocalKubernetesExecutor
- Task를 구현하는 것이 Operator
Airflow Executor
- Task들을 관리하고 실행하는 역할
- 병렬/일렬 실행
- 어느 Worker node에서 실행할지 등
SequentialExecutor
- default
- Sqlite와 같은 싱글 스레드 DB에서만 사용 가능
- 병렬성 지원 X. 일렬 실행만 가능
LocalExecutor
- 멀티 스레드를 지원하는 PostgreSQL, MySQL 등의 DB를 메타데이터 DB에서 사용 가능
- 병렬 가능
- but 워커 노드가 1개(Master 노드)인 경우에만 가능
CeleryExecutor
- 다수의 Worker 노드가 있는 경우 사용
- Celery 큐를 사용해 Task들을 Worker 노드로 분산해서 실행 (Scale out)
KubernetesExecutor
- K8s 클러스터를 사용하여 Task들을 독립된 환경에서 사용
- 모든 Task/DAG는 Docker Image로 구현되어 있어야 함
LocalKubernetesExecutor
,CeleryKubernetesExecutor
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를 동시에 사용하는 방법을 제공
- Airflow 로드가 전체적으로 큰데, 소수의 Task만 Isolation을 필요로 하는 경우
LocalKubernetesExecutor
- LocalExecutor + KubernetesExecutor
2. Docker
Docker의 목표
-
소프트웨어를 일관되게 빌드, 실행 및 배포
- Docker file: 이 애플리케이션이 어떤 sw에 대한 dependency가 있고, 어떤 환경 변수들이 세팅되어야 하는지 등등을 적어둠
- Docker Image
- Docker Container
VM
- Virtual Machine
- AWS EC2가 대표적인 VM
- 하드웨어를 추상화하여 한 컴퓨터 위에 가상 컴퓨터를 올리는 것
- 즉, 컴퓨터 하드웨어 단의 추상화
- Host OS 위 Hypervisor 존재
- 그 위에 자체 OS를 가짐
- 장점
- 소프트웨어를 실행하기 위한 독립적이고 분리된 공간 제공
- 다수의 소프트웨어를 각 VM단에서 독립적으로 실행 가능
- HW 간의 충돌이 사라짐
- 단점
- 각 VM은 자신만의 OS를 필요로 함 (가상 HW 위에서 돌기 때문)
- 유료 OS라면 라이센스 비용 필요
- 시작하는 데 오래 걸림
- 자원을 많이 사용함 (VM들끼리 자원을 나눠써야 함)
- 각 VM은 자신만의 OS를 필요로 함 (가상 HW 위에서 돌기 때문)
Docker Container
- 소프트웨어를 실행하기 위한 독립적이고 분리된 공간
- 자체 파일 시스템을 가지고 있음 (Volume이라고 부름)
Host OS | 지원하는 컨테이너 OS |
---|---|
Mac | 경량화된 리눅스 VM이 동작 </br> Docker Engine에 많이 의존 </br> Docker Engine 자체가 리눅스 기반 VM |
Windows | 윈도우, 리눅스 |
Linux | 리눅스 |
- 장점
- 소프트웨어를 실행하기 위한 독립적이고 분리된 공간 제공
- 다수의 소프트웨어를 각 컨테이너단에서 독립적으로 실행 가능
- 자원 소비가 적음 (light weight)
- 몇 십개에서 몇 백개의 컨테이너 실행 가능
- 호스트 OS 사용 (별도 비용 없음)
- 빠르게 실행됨
- 소프트웨어를 실행하기 위한 독립적이고 분리된 공간 제공
- 단점
- 많은 수의 Docker Container를 관리하는 것은 쉽지 않음
- 호스트 OS를 사용하기 때문에 Cross-platform comparibility를 항상 지원하지 않음
- 하지만 보통 리눅스를 기반으로 개발하기 때문에 괜찮음
- GUI 소프트웨어 개발에 적합하지 않음
3. Docker 프로그램 개발 프로세스
하이레벨 Docker 사용 프로세스
- 대상 소프트웨어 선택
- 다수의 컴포넌트로 구성되는 소프트웨어라면 각각이 Docker Image로 만들어져야 할 수도 있음
- 그 수만큼 컨테이너로 돌려야 하기 때문
- 하나의 Image로 구성할 수도 있음
- 다수의 컴포넌트로 구성되는 소프트웨어라면 각각이 Docker Image로 만들어져야 할 수도 있음
- 이를 Docker Image로 빌드: Dockerization
Dockerfile
이라는 텍스트 파일로 세부 정보 기술- 해당 소프트웨어를 이미지로 바꾸기 위해 Docker에게 주는 명령들을 포함
- Docker Image: 하나의 Docker Container안에서 실행됨
- Dockerfile을 기준으로 만들어지며, 소프트웨어를 실행하기 위해 필요한 모든 것을 포함
- 기본이 되는 컴포넌트
FROM
,RUN
,ENTRYPOINT
/CMD
Docker Image 구성 요소
- 기본 OS와 같은 소프트웨어의 실행 환경
FROM
- Linux, Python 등
- 소프트웨어 자체 (코드)
- 소프트웨어가 필요로 하는 라이브러리
RUN
- 파일 시스템 스냅샷 (Volume)
- 스택화된 형태로 구현됨
- 환경 설정 변수
- 빌드할 때 변수
- 실행할 때 변수
- 메타 데이터
- 이미지 자체에 대한 정보 (버전, 작성자, 설명 등)
- 위 정보와 설치 관련 실행 순서 등이 Dockerfile에 기술됨
- Docker Image는 다수의 파일로 구성됨 (
docker image ls
)
Docker Image 실행
- Docker Container를 통해 Docker Image 안의 소프트웨어를 실행
- 컨테이너는 자체 파일 시스템(Volume)을 가진 특수한 프로세스로 이미지의 파일 시스템이 로딩됨
- 이미지를 컨테이너 안에서 실행
docker run ...
Docker Image 등록: Docker Hub
- Docker Registry는 Docker Image들의 보관소
- On-prem registry와 Cloud registry가 존재
- Docker Hub가 가장 유명
- 이곳에 등록을 하면 회사 내, 혹은 퍼블릭하게 이미지 공유 가능
- GitHub과 유사점이 많음
Docker Hub
- Docker Image를 공유하고 찾기 위한 서비스
- Teams & Organizations
- Public과 Private Repo 제공
- Official Images
- Github과 연동을 통한 Automated Build 제공
4. 실습1) Hello World 프로그램
- node.js로 만든 아주 간단한 웹 서비스
- app.js만 존재
- node 런타임 환경이 필요
- 보통 이를 실행하려면
node app.js
직접 설치/실행 시
- OS 선택
- Node 설치
- 코드 복사
- 프로그램 실행 (
node app.js
)
- 이 내용을 Dockerfile에 기술하면 Docker Image 생성 가능!
Dockerfile 생성
- Docker에게 소프트웨어 설치 명령 기술
1) 베이스 이미지 기술 (FROM)
2) 코드 복사
3) 코드 실행
FROM node:alpine
COPY . /app
WORKDIR /app
CMD node app.js
FROM
: OS 종류- apline: 경량 리눅스
COPY
: 코드 복사WORKDIR
: Working directoryCMD
: 실행 명령어
기타 키워드
ARG
- Docker Image를 만들 때 사용되는 변수 지정
- 최종 이미지에는 안들어감
ENV
- 컨테이너가 실행될 때 사용되는 환경변수
- 최종 이미지에 저장됨
USER
- 컨테이너를 실행할 때 사용할 유저 ID
EXPOSE
- 서비스 사용 포트 번호
RUN
- 빌드 시 실행되어야 하는 명령어들이 지정됨
docker build
RUN apt-get update && apt-get install -y curl
- 빌드 시 실행되어야 하는 명령어들이 지정됨
CMD vs. ENTRYPOINT
- 둘 다 Container가 시작할 때 실행되어야 하는 명령어를 지정하는데 사용
docker run [image]
- 우선 순위가 있음
- 둘 다 한 Dockerfile에서 여러번 실행되면 각각 마지막 것만 사용됨
- 아래의 경우
docker run
실행시 동일한 결과가 나옴 - 일반적으로
CMD
만 사용하는 것이 좋은 practice라고 여겨짐!
CMD ["command1.sh"]
CMD ["command2.sh"]
ENTRYPOINT ["command1.sh"]
ENTRYPOINT ["command2.sh"]
-
CMD나 ENTRYPOINT 중 하나만 지정되면 그것이 컨테이너가 실행될 때 실행
-
CMD: override 될 수 있음
FROM debian:buster
COPY . /myproject
RUN apt-get update ..
CMD ["./cmd1.sh"]
docker run my-image
# ./cmd1.sh가 기본으로 실행됨
docker run my-image cmd2.sh
# cmd2.sh가 실행됨 (override)
-
CMD와 ENTRYPOINT가 같이 사용되면 ENTRYPOINT가 기본 명령이 되고, CMD가 파라미터가 됨!
-
ENTRYPOINT:
--entrypoint
옵션을 통해서만 override 가능
FROM debian:buster
COPY . /myproject
RUN apt-get updaate ...
ENTRYPOINT ["entrypoint1.sh"]
CMD ["param1", "param2"]
docker run my-image
# entrypoint1.sh param1 param2
docker run my-image cmd2
# entrypoint1.sh cmd2
docker run --entrypoint="/cmd3.sh" my-image
# /cmd3.sh param1 param2
정리
- 최대한
CMD
만 사용 - ENTRYPOINT를 사용하면 실행 시 타이핑을 덜 할 수 있음
- 하지만 꼭 ENTRYPOINT가 필요한 경우는 없음
Dockerfile 예시
FROM python:3.7-slim-buster
# build할 때에만 사용
ENV DEBIAN_FRONTEND noninteractive
# 최종 이미지에도 저장됨 -> 이후 CMD 등에서도 사용 가능
ARG AIRFLOW_USER_HOME=/user/local/airflow
ARG AIRFLOW_VERSION=1.10.9
COPY config/airflow.cfg ${AIRFLOW_USER_HOME}/airflow.cfg
# build할 때 실행되는 명령
RUN chown -R airflow: ${AIRFLOW_USER_HOME}
# 3개의 TCP 포트 오픈
# 8080: airflow webserver
EXPOSE 8080 5555 8793
USER airflow
WORKDIR ${AIRFLOW_USER_HOME}
ENTRYPOINT ["/entrypoint.sh"]
CMD ["webserver"]
# /entrypoint.sh webserver 스크립트가 실행됨
Docker Image 생성
docker build --platform linux/amd64 -t hello-world-docker .
--platform linux/amd64
- Apple chip 기반 맥에서 빌드하는 경우 그 이미지는 ARM 기반 아키텍처로 만들어지기 때문에 일반 리눅스에서 돌아가지 않음
--platform
옵션을 사용해서 linux/amd64로 지정
-t hello-world-docker
- 태그 지정
-t hello-world-docker:version
으로 버전을 지정할 수도 있음- 버전 지정하지 않으면 TAG값으로 기본으로 latest가 들어감
.
- docker build를 실행하면 현재 디렉터리에 있는 Dockerfile로
RUN
명령이 실행됨
- docker build를 실행하면 현재 디렉터리에 있는 Dockerfile로
Docker Container로 실행
docker run hello-world-docker
# Hello World!
- Dockerfile에서 CMD 명령이 실행됨
-p
옵션: 포트 매핑-
-v
옵션: volume 매핑 - 이 이미지를 다른 컴퓨터에서 실행하고자 한다면 Docker Registry로 먼저 등록
5. Docker Hub
1) Docker Hub 회원등록
2) repo 만들기
3) Docker Registry에 등록
docker image ls
docker tag hello-world-docker:latest bokyung/hello-world-docker:latest
# docker image를 새로운 이미지 이름과 버전으로 바꾸어줌
docker image ls
docker login --username=bokyung # pwd 입력
docker push bokyung/hello-world-docker
# 같은 이름의 docker hub server로 업로드
리눅스 서버 사용
-
https://labs.play-with-docker.com/ 에서 4시간 동안 서버 하나를 무료로 사용할 수 있음
-
Docker Hub 계정으로 로그인
-
터미널 윈도우는 Option + Enter로 최대화
docker version
docker pull bokyung/hello-world-docker
docker image ls
docker run bokyung/hello-world-docker
run
명령 실행 시 로커에 다운받은 이미지가 없다면 docker hub에서 pull을 알아서 수행함
docker run vs. docker exec
docker run
: 새로 컨테이너를 실행하는 것docker exec
: 이미 실행된 컨테이너에 작업을 하는 것- 인자로 container id가 필요함
-it
: interactive terminal. 실행중인 도커에 접근해서 입력한 명령어를 실행하고 그 상태를 유지시켜 주는 역할
- 두 명령 모두
--user root
또는-u root
를 통해 루트 유저로 연결 가능
docker image 이름
docker image ls
와docker images
는 같은 명령
-
TAG: 버전 / 변형 정보
-
/
앞 (네임스페이스): docker hub에서 받은 이미지의 경우 account id나 name space가 있음 - docker tag 명령의 경우 별칭을 만들어주는 것임 (이름을 바꾸는 것이 아님!)
docker tag hello-world-docker bokyung/hello-world-docker
- 별칭을 만들어준 경우 IMAGE ID가 같음!
- Image 이름 자체는 REPOSITORY 이름과 TAG로 구성됨
- 한 번에 쓰는 경우
:
을 사이에 두고 같이 씀 - redis:13
- bokyung/hello-world-docker:latest
- 한 번에 쓰는 경우
docker tag
- Docker Image의 버전이나 변형을 나타내는 문자열
- default:
latest
- 이미지의 부가 정보를 나타냄
- default:
- Docker Image의 이름에서
:
뒤에 해당함- ubuntu: 18.04
- bitnami/airflow
- latest
- node:alpine
Docker Image 이름
- docker image ls에서 REPOSITORY에 해당
- Docker Hub에서 다운로드 받은 것이라면 account id(name space)를 포함할 수 있음
- 포맷:
REPO이름:태그
- DOCKER OFFICIAL IMAGE의 경우에는 네임스페이스가 없음
- ubuntu:18.04
- node:alpine
6. 실습
Image build & push
credentials Error
docker login
에서
Error saving credentials: error storing credentials - err: docker-credential-desktop resolves to executable in current directory (./docker-credential-desktop), out:
Error 발생시
rm -rf ~/.docker
실행 후 다시 로그인!
Image 실행
https://labs.play-with-docker.com
7. 도커에서 우분투 실행
- 리눅스 커널: 리눅스의 핵심 부분
- 리눅스 배포판
- 우분투: 가장 많이 사용되며, 데비안에 기반해서 만들어짐
- 데비안
- 알파인: 경량화 리눅스 배포판
- 페도라
- CentOS
우분투 설치
- 보통은 이미지를 pull하고 run을 해야하지만, 그냥 run을 해도 이미지가 없을시 자동으로 pull 후 run을 해줌
그냥 run을 하면 바로 끝나버림
-it
옵션을 주면 interactive terminal 실행
8. 도커에서 MySQL 실행
docker pull mysql/mysql-server:8.0
docker run --name=mysql_container mysql/mysql-server:8.0
# docker exec를 실행할 때 container id를 찾기 위해 docker ps 명령을 실행하지 않아도 됨
docker logs mysql_container 2>&1 | grep GENERATED
# MySQL root 계정의 패스워드 찾기
docker exec -it mysql_container mysql -uroot -p
명령어
docker run --name
- Container ID 대신 사용 가능한 이름 지정
docker logs
- Container 쪽에서 생성된 stdout, stderr 단의 로그를 읽어옴
--follow
옵션을 사용하면 로그가 계속적으로 스트리밍 됨