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와 같은 컨테이너 기술의 도입으로 해결 가능

스크린샷 2023-12-18 오후 2 49 13

해결책

  • 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

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

스크린샷 2023-12-18 오후 3 15 56

Docker의 목표

  • 소프트웨어를 일관되게 빌드, 실행 및 배포

  • Docker file: 이 애플리케이션이 어떤 sw에 대한 dependency가 있고, 어떤 환경 변수들이 세팅되어야 하는지 등등을 적어둠
  • Docker Image
  • Docker Container

VM

  • Virtual Machine
    • AWS EC2가 대표적인 VM
    • 하드웨어를 추상화하여 한 컴퓨터 위에 가상 컴퓨터를 올리는 것
      • 즉, 컴퓨터 하드웨어 단의 추상화


스크린샷 2023-12-18 오후 3 34 54

  • Host OS 위 Hypervisor 존재
  • 그 위에 자체 OS를 가짐


  • 장점
    • 소프트웨어를 실행하기 위한 독립적이고 분리된 공간 제공
    • 다수의 소프트웨어를 각 VM단에서 독립적으로 실행 가능
      • HW 간의 충돌이 사라짐
  • 단점
    • 각 VM은 자신만의 OS를 필요로 함 (가상 HW 위에서 돌기 때문)
      • 유료 OS라면 라이센스 비용 필요
      • 시작하는 데 오래 걸림
    • 자원을 많이 사용함 (VM들끼리 자원을 나눠써야 함)

Docker Container

  • 소프트웨어를 실행하기 위한 독립적이고 분리된 공간
  • 자체 파일 시스템을 가지고 있음 (Volume이라고 부름)

스크린샷 2023-12-18 오후 3 40 09


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로 빌드: Dockerization
    • Dockerfile이라는 텍스트 파일로 세부 정보 기술
      • 해당 소프트웨어를 이미지로 바꾸기 위해 Docker에게 주는 명령들을 포함
    • Docker Image: 하나의 Docker Container안에서 실행됨
      • Dockerfile을 기준으로 만들어지며, 소프트웨어를 실행하기 위해 필요한 모든 것을 포함
    • 기본이 되는 컴포넌트
      • FROM, RUN, ENTRYPOINT/CMD

스크린샷 2023-12-18 오후 4 08 46

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과 유사점이 많음

스크린샷 2023-12-18 오후 4 18 03

Docker Hub

https://hub.docker.com/

  • 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 directory
  • CMD: 실행 명령어


기타 키워드

  • 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 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 lsdocker images는 같은 명령

스크린샷 2023-12-18 오후 7 13 11

  • TAG: 버전 / 변형 정보

  • / 앞 (네임스페이스): docker hub에서 받은 이미지의 경우 account id나 name space가 있음

  • docker tag 명령의 경우 별칭을 만들어주는 것임 (이름을 바꾸는 것이 아님!)
    • docker tag hello-world-docker bokyung/hello-world-docker
    • 별칭을 만들어준 경우 IMAGE ID가 같음! 스크린샷 2023-12-18 오후 7 16 40
  • Image 이름 자체는 REPOSITORY 이름과 TAG로 구성됨
    • 한 번에 쓰는 경우 :을 사이에 두고 같이 씀
    • redis:13
    • bokyung/hello-world-docker:latest

docker tag

  • Docker Image의 버전이나 변형을 나타내는 문자열
    • default: latest
    • 이미지의 부가 정보를 나타냄
  • 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 스크린샷 2023-12-18 오후 7 22 32

6. 실습

Image build & push

스크린샷 2023-12-18 오후 7 29 58

스크린샷 2023-12-18 오후 7 46 56

스크린샷 2023-12-18 오후 7 47 30

스크린샷 2023-12-18 오후 7 47 48

스크린샷 2023-12-18 오후 8 25 13

스크린샷 2023-12-18 오후 8 24 46

스크린샷 2023-12-18 오후 8 26 09

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

스크린샷 2023-12-18 오후 8 30 36

스크린샷 2023-12-18 오후 8 32 24

스크린샷 2023-12-18 오후 8 32 34

7. 도커에서 우분투 실행

  • 리눅스 커널: 리눅스의 핵심 부분
  • 리눅스 배포판
    • 우분투: 가장 많이 사용되며, 데비안에 기반해서 만들어짐
    • 데비안
    • 알파인: 경량화 리눅스 배포판
    • 페도라
    • CentOS

우분투 설치

  • 보통은 이미지를 pull하고 run을 해야하지만, 그냥 run을 해도 이미지가 없을시 자동으로 pull 후 run을 해줌

스크린샷 2023-12-18 오후 8 42 02
그냥 run을 하면 바로 끝나버림

스크린샷 2023-12-18 오후 8 42 39

-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


스크린샷 2023-12-18 오후 9 39 41

image

스크린샷 2023-12-18 오후 9 46 25

스크린샷 2023-12-18 오후 9 45 54

명령어

  • docker run --name
    • Container ID 대신 사용 가능한 이름 지정
  • docker logs
    • Container 쪽에서 생성된 stdout, stderr 단의 로그를 읽어옴
    • --follow 옵션을 사용하면 로그가 계속적으로 스트리밍 됨