Airflow 운영상의 어려움 (다수의 소프트웨어에도 적용됨)
관리해야하는 DAG의 수가 100개를 넘어간다면 ?
- 데이터 품질이나 데이터 리니지 이슈 이외에도 다양한 이슈들이 발생
- 어떤 이슈들이 있을까?
- 라이브러리 충돌
- Worker의 부족 : 처음엔 싱글노드로 돌아서 서버 하나로 돌리는데, 많아지면 하나론 부족. Multi node cluster로 바뀌어야함. 다수의 서버를 airflow cluster에 할당할 경우, 놀고 있는 서버로 인한 자원 낭비, 각 기술마다 서버를 별도로 구성하면 관리가 어려워짐. CPU, 디스크, 메모리가 놀고 있는 경우가 많아지게 됨.
- Worker 서버들의 관리와 활용도 이슈
1. 라이브러리 충돌
- 라이브러리/모듈의 충돌 이슈가 발생하기 시작함
- DAG에 따라 실행에 필요한 라이브러리/모듈이 달라지기 시작
- 이로 인해 DAG 혹은 Task별로 별도의 독립공간을 만들어주는 것이 필요
- Docker to the rescue
- Dag 혹은 Task 코드를 Docker Image로 만들고 이를 독립된 공간(Docker Container)안에서 실행
2. Worker의 부족
- Scale Up : 서버 사양 올리는 것, 어느 시점 한계에 도달
- Scale Out : 워커 노드를 계속 증가. 클라우드 기반의 airflow 서비스 사용 고려
- k8s와 같은 컨테이너 기술 사용
3. 낮은 Server Utilization Issue
- 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에서만 사용 가능
- 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가 가장 필요한 이유는 내가 만든 프로그램이 내 컴퓨터 내 개발환경에서는 잘돌아가는데 다른사람이 가져가 쓰는 순간 혹은 테스트 프로덕션 환경에서 안돌아가는 경우.(이런경우가 꽤 많음)
- 라이브러리 혹은 모듈의 충돌이 가장 큰 이유
- 설치 과정에서 중요한 파일이 빠짐
- 환경 설정이 안 맞는 것이 존재
그중에 가장 효율적이고 많이 사용되는 솔루션이 Docker
내 컴퓨터 환경을 그대로 패키징해서 다른 이에게 줄 수 있다면 ?
- Docker Image : 이렇게 독립적으로 완전하게 만들어진 패키지,내가 만든 소프트웨어를 완전한 형태로 모든 디펜던시가 있는 라이브러리를 포함해서 패키징. 이걸 만드는걸 build한다고 한다.
- Docker Container : 이 Docker Image를 독립된 환경에서 실행한 것. os와 다른 컨테이너와 별도로 docker engine위에서 돈다 (host 컴퓨터 위가 아닌). host os와 다른 컨테이너와 충돌이 생기지 않게 보장. VM과 비슷하지만 훨씬 light-weight. VM은 이자체에 OS를 가지므로 헤비하고 속도도 느리지만 컨테이너는 docker container가 다수로 돌 수 있는 세팅.

개발에 사용했던 컴퓨터 환경을 그대로 패키징해서 다른사람에게 줄 수 있다면 다른사람도 내가 개발한 것처럼 사용할 수 있다.
docker-compose란 다수의 컨테이너로 구성된 애플리케이션이 있을 때 사용되는 명령어.
Docker의 목표
- 소프트웨어를 일관되게 빌드하고 실행하고 배포
Docker File -Build-> Docker Image -Run-> Docker Container
Virtual Machines vs Docker Containers
Virtual Machine 소개
- AWS의 EC2가 대표적인 Virtual Machine (VM)
- 하드웨어를 추상화하여 한 컴퓨터 위에 가상 컴퓨터를 올리는 것
- 즉 컴퓨터 하드웨어 단의 추상화

Virtual Machines: 보통 하나의 컴퓨터 위에 다수의 VM을 실행하는 것이 일반적. 이 안에서 소프트웨어가 동작
VM을 생성하고 관리하기 위한 소프트웨어 : VMWare, VirtualBox, Hyper-v, ...
Hypervisors: VM을 생성하고 관리하는 레이어
하드웨어 안에 또다른 하드웨어가 돌아가는 꼴
Virtual Machine의 장단점
- 장점
- 소프트웨어를 실행하기 위한 독립적이고 분리된 공간을 제공. 소프트웨어 호환성에 있어서 진짜 컴퓨터 쓰는 느낌.
- 다수의 소프트웨어를 각 VM단에서 독립적으로 실행가능(하드웨어간의 충돌이슈 사라짐)
- 단점
- 각 VM은 자신만의 OS를 필요로 함 (가상 하드웨어 위에서 돌기 때문)
- 유료 OS(Windows, Redhat 등)라면 라이센스 비용 필요
- 그러다보니 시작하는데 오래 걸림
- 자원을 많이 사용함 (VM들끼리 자원을 나눠써야함), 메모리도 더 쓰고 디스크도 더 쓰고
Docker Container

Docker 프로그램 개발 프로세스
Docker로 소프트웨어를 패키징(빌드)하는 프로세스
하이레벨 Docker 사용 프로세스
먼저 대상 소프트웨어를 선택
- 다수의 컴포넌트로 구성되는 소프트웨어라면 각각이 Docker Image로 만들어져야할 수도 있음
- 이를 Docker Image로 빌드하자: Dockerization이라고 부른다.
- Dockerfile이란 텍스트 파일로 세부 정보를 기술
- 해당 소프트웨어를 이미지로 바꾸기 위한 Docker에게 주는 명령들을 포함
- Docker Image : 하나의 Docker Container안에서 실행됨
- Dockerfile을 기준으로 만들어지며 소프트웨어를 실행하기 위해 필요한 모든 것을 포함

Docker Image의 구성 요소
- 기본 OS (리눅스라면 우분트, 데비안 등등)와 같은 소프트웨어의 실행환경
- 소프트웨어 자체 (코드)
- 소프트웨어가 필요로 하는 라이브러리
- 파일 시스템 스냅샷: 이는 스택화된 형태로 구현됨 (뒤에서 더 설명)
- 환경 설정 변수: 빌드할 때 변수와 실행 때 변수 두가지가 존재 => ENV, ARG
- 메타 데이터: 이미지 자체에 대한 정보 (버전(안붙이면 latest), 작성자, 설명 등등)
위 정보와 설치 관련 실행 순서들이 Dockerfile에 기술됨
Docker Image는 다수의 파일로 구성됨 ("docker image ls")
Docker Image의 실행
- Container를 통해 Docker Image안의 소프트웨어를 실행
- Container는 자체 파일 시스템을 가진 특수한 프로세스로 이미지의 파일 시스템이 로딩됨
- Image를 Container 안에서 실행
Docker Image의 등록 : Docker Hub
- Docker Registry는 Docker Image들의 보관소
- On-prem registry(회사내에서만 사용,private)와 Cloud registry(public or private)가 존재
- docker hub가 가장 유명하다.
- 여기에 등록을 하면 회사내 혹은 퍼블릭하게 이미지를 공유 가능
Docker Hub이란?
- https://hub.docker.com
- Docker가 제공해주는 서비스로 Docker Image를 공유하고 찾기 위한 서비스
- Teams & Organizations
- Official Images
- Github과 연동을 통한 Automated Build 제공
Practice 1 : 간단한 Hello World 프로그램
node.js로 만든 간단한 프로그램을 단계별로 이미지 빌드부터 최종적으로 다른 서버에서 실행까지 전체 과정을 다뤄보는 practice
만들려는 프로그램 개요
- Node.js로 구성된 초간단 웹 서비스
a. app.js가 전부
b. 하지만 node 런타임 환경이 필요
- 보통 이를 실행하려면
a. node app.js
- 단 node 환경이 설정되어 있어야함
Docker 없이 만들려는 프로그램 실행 시
- 직접 설치/실행 시 순서
a. OS선택
b. Node 설치
c. 코드 복사
d. 프로그램 실행 (node app.js)
-> 이 내용을 Dockerfile에 기술하면 Docker Image 생성이 가능하다.
Dockerfile의 생성
- Docker에게 소프트웨어 설치 명령을 기술
- 먼저 베이스 이미지를 기술 (FROM)
- 다음으로 코드 복사
- 마지막으로 코드 실행
FROM node:alpine
COPY . /app
WORKDIR /app
CMD node app.js
Dockerfile 사용 가능 기타 키워드
- ARG : Docker Image를 만들 때 사용되는 변수 지정. 최종 이미지에는 안 들어감.
- ENV : 컨테이너가 실행될 때 사용되는 환경변수. 최종 이미지에 저장됨
- USER : 컨테이너를 실행할 때 사용할 유저 ID
- EXPOSE : 서비스 사용 포트번호
- RUN :
- 빌드시 실행되어야 하는 명령들이 지정됨 (docker build)
- RUN apt-get update && apt-get install -y curl
Dockerfile 키워드 : CMD vs ENTRYPOINT (1)
-
Container가 시작할 때 실행되어야 하는 명령어를 지정하는데 사용 (docker run)
- 굉장히 흡사한 기능을 제공하지만 우선 순위가 있음
-
둘다 한 DOCKERFILE에서 여러번 실행되면 각각 마지막 것만 사용됨
-
아래의 경우 docker run 실행 시 동일한 결과가 나옴
-
CMD
CMD ["command1.sh"]
CMD ["command2.sh"]
-
ENTRYPOINT
ENTRYPOINT ["command1.sh"]
ENTRYPOINT ["command2.sh"]
둘다 쓰이면 ENTRYPOINT가 우선순위
ENTRYPOINT 안쓰고 CMD만 쓰는게 best practice.
둘다 컨테이너 실행 시 command2.sh 스크립트가 실행됨(마지막것만)
Dockerfile 키워드 : CMD vs ENTRYPOINT (2)
- CMD나 ENTRYPOINT 중 하나만 지정되면 그게 container가 실행될 때 실행
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가 실행됨
Dockerfile 키워드 : CMD vs ENTRYPOINT (3)
- 둘이 한 DOCKERFILE에서 같이 지정가능함
- 둘이 같이 사용되면 ENTRYPOINT가 기본 명령이 되고 CMD가 인자를 제공
- ENTRYPOINT는 --entrypoint 옵션을 통해서만 덮어쓰기가 가능

CMD or ENTRYPOINT ?
- 최대한 CMD만 사용
- ENTRYPOINT를 사용하면 실행시 타이핑을 덜 할 수 있음
- 파라미터를 지정해주면 되기 때문이지만 감춰지기 때문에 오히려 혼란을 줄 수 있음
요약 :
1. ENTRYPOINT가 있으면 CMD값이 파라미터로 실행됨
2. 아니면 CMD가 실행됨
Dockerfile 살펴보기 ex) Airflow
FROM python:3.7-slim-buster
LABEL maintainer="Puckel_"
ENV DEBIAN_FRONTEND noninteractive
ENV TERM linux
ARG AIRFLOW_VERSION=1.10.9
ARG AIRFLOW_USER_HOME=/usr/local/airflow
ARG AIRFLOW_DEPS=""
ARG PYTHON_DEPS=""
ENV AIRFLOW_HOME=${AIRFLOW_USER_HOME}
ENV LANGUAGE en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LC_ALL en_US.UTF-8
ENV LC_CTYPE en_US.UTF-8
ENV LC_MESSAGES en_US.UTF-8
RUN set -ex \
&& buildDeps=' \
freetds-dev \
libkrb5-dev \
libsasl2-dev \
libssl-dev \
libffi-dev \
libpq-dev \
git \
' \
&& apt-get update -yqq \
&& apt-get upgrade -yqq \
&& apt-get install -yqq --no-install-recommends \
$buildDeps \
freetds-bin \
build-essential \
default-libmysqlclient-dev \
apt-utils \
curl \
rsync \
netcat \
locales \
&& sed -i 's/^# en_US.UTF-8 UTF-8$/en_US.UTF-8 UTF-8/g' /etc/locale.gen \
&& locale-gen \
&& update-locale LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 \
&& useradd -ms /bin/bash -d ${AIRFLOW_USER_HOME} airflow \
&& pip install -U pip setuptools wheel \
&& pip install pytz \
&& pip install pyOpenSSL \
&& pip install ndg-httpsclient \
&& pip install pyasn1 \
&& pip install apache-airflow[crypto,celery,postgres,hive,jdbc,mysql,ssh${AIRFLOW_DEPS:+,}${AIRFLOW_DEPS}]==${AIRFLOW_VERSION} \
&& pip install 'redis==3.2' \
&& if [ -n "${PYTHON_DEPS}" ]; then pip install ${PYTHON_DEPS}; fi \
&& apt-get purge --auto-remove -yqq $buildDeps \
&& apt-get autoremove -yqq --purge \
&& apt-get clean \
&& rm -rf \
/var/lib/apt/lists/* \
/tmp/* \
/var/tmp/* \
/usr/share/man \
/usr/share/doc \
/usr/share/doc-base
COPY script/entrypoint.sh /entrypoint.sh
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 ["/entrypoint.sh"]
CMD ["webserver"]
ENV: 최종 이미지에 들어감
ARG : 빌드할 때만 사용하며 이미지에는 안들어감
RUN ~ : 빌드할 때 실행되는 명령
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로 지정
-t hello-world-docker : -t는 태그를 지정하는 것으로 뒤에서 더 설명


Docker Image 확인
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world-docker latest cb6c63816878 22 minutes ago 179MB

Docker Container로 실행
$ docker run hello-world-docker
Hello Docker!
- 만일 이 이미지를 다른 컴퓨터에서 실행하고자 한다면?
- Docker Registry (예를 들면 Docker hub)으로 먼저 등록
Docker Hub 소개
Docker hub에 repo로 등록하고 새로운 리눅스 서버에서 다운로드받은 후에 거기에서 컨테이너 구동
Docker Registry에 등록
$ docker image ls
$ docker tag hello-world-docker:latest yjbenkang/hello-world-docker:latest
$ docker image ls

$ docker login --username=yjbenkang
$ docker push yjbenkang/hello-world-docker

- 위의 명령 실행 후 Docker Hub에서 레포에 이미지가 잘 올라갔는지 결과 확인

Docker Hub로부터 받은 Image 실행
다른 서버에서 이미지를 다운로드 받아 컨테이너로 실행하기
-
https://labs.play-with-docker.com/ 를 사용해서 4시간동안 서버를 하나 무료로 사용
-
Docker Hub에 로그인이 되어있다면 그냥 계정 연결을 허용하면 됨

-
터미널 윈도우를 Option + Enter(윈도우는 Alt+Enter)로 최대화하고 아래 명령 실행
- docker version
- docker pull yjbenkang/hello-world-docker
- docker image ls
- docker run yjbenkang/hello-world-docker


너무 느린관계로 run은 실행못했음..
만일 로컬에 다운로드 받은 이미지가 없다면 docker hub에서 pull을 알아서 수행함
실행시 아래 에러가 난다면 build할때 --platform 옵션 사용
필요:
WARNING: The requested image's platform
(linux/arm64/v8) does not match the detected host
platform (linux/amd64/v3) and no specific platform was
requested exec /usr/local/bin/docker-entrypoint.sh: exec
format error
Docker Desktop에서 로그인
- Docker Desktop에서 Docker Hub으로 로그인해두면 Desktop에서 Pull, Push 등을 수행 가능
위에서 사용한 Docker 명령 요약
push & pull은 docker login으로 인증 먼저
- docker version
- docker build -t [이미지이름]:[버전정보]
- docker push : docker registry에 업로드
- docker tag : 내가 이미 만든 이미지의 이름을 다른 이름으로 변경, 이미지 이름 바꿀 때 혹은 이미지의 버전이나 태그를 다른걸로 바꿀 때 사용 (별칭을 만드는거지 이름을 아예 새로 바꾸는게 아님)
- docker pull : test나 prod 서버에서 남이 만들어서 업로드해놓은 도커 이미지를 내가 작업중인 서버로 다운로드 받을 때 사용
- docker run : 컨테이너 시작, dockerfile의 entrypoint 세팅되어있으면 cmd의 값을 파라미터로 받아서 할거고, cmd만 있으면 cmd가 실행됨
- p option (port mapping)
- v option (volume mapping)
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 yjbenkang/hello-world-docker
-
Docker image의 실제 ID는 IMAGE ID임
-
Image 이름 자체는 REPOSITORY 이름과 TAG로 구성됨
- 한번에 쓰는 경우 :을 사이에 두고 같이 씀 -> redis:13, yjbenkang/hello-world-docker:latest
-
docker image ls에서 Repository에 해당
- Docker Hub에서 다운로드받은 것이라면 어카운트ID(네임스페이스)를 포함할 수 있음
- 하지만 공식이미지는 네임스페이스가 없음
-
포맷: Repo이름:태그
- 예: yjbenkang/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
Practice 2 : Ubuntu 컨테이너로 실행
리눅스 커널과 배포판
- 리눅스 커널: 리눅스의 핵심부분(자동차로 치면 엔진). Linus B. Torvalds가 1992년에 처음 공개

어떤 리눅스 배포판이 있는가?
- 우분투 : 가장 많이 사용되며 데비안에 기반해서 만들어진 리눅스 배포판
- 데비안
- 알파인 : 임베드 시스템에서 사용할 용도로 만들어진 경량화 리눅스 배포판
- 페도라
- 센트OS
...
배포판에 따라 다른 패키지 매니저가 존재
npm, yarn, pip, apt, NuGet
우분투 설치 후 다양한 명령 실행해보기
보통은 docker image를 pull하고 run을 해야함. 바로 run하면?
- docker run ubuntu
- docker ps
- docker ps -a
docker run -it ubuntu
- nano
- apt list
- apt install nano
- apt update
- apt install nano
- nano
- apt remove nano

local에서 이미지를 찾을 수 없어 dockerhub에서 퍼블릭 이미지를 가져온다.
docker ps를 해보면 ubuntu 컨테이너를 확인할 수 없다.
docker ps -a를 해보면 종료되었던 ubuntu 컨테이너를 확인할 수 있다.
docker run ubuntu로 그냥 실행하면 바로 끝나버린다.
docker run -it ubuntu
-it : interactive옵션이다. ubuntu로 오피셜 이미지를 주고 실행

이번에는 shell script사용해서 우분투 도커 컨테이너로 이동됨. root 어카운트로 들어간걸 확인할 수 있고, 패키지 설치가 가능해짐
apt update로 outdate된 apt 를 업뎃해준다.

apt install nano잘됨
nano는 에디터.끝내고 싶으면 ctrl + x

Practice 3 : 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
- 어떤 프로그램 실행할 것인지에 대해서 mysql 입력해서 mysql shell을 실행, -u옵션으로 root유저로 로그인, -p으로 password입력하겠다 선언
여기서 사용하는 Docker 명령
- docker run --name
- 기억하기 쉬운 이름을 docker ps로 찾은 Container ID대신 사용 가능
- docker logs
- Container쪽에서 생성된 stdout, stderr단의 로그를 읽어옴
- --follow 옵션을 사용하면 로그가 계속적으로 스트리밍이 됨
