[TIL 46일자] 데브코스 데이터엔지니어링

·2023년 6월 13일
0

데브코스

목록 보기
42/55
post-thumbnail

📚 오늘 공부한 내용

1. Airflow 운영의 어려움

  • 관리해야 하는 DAG의 수가 100 개가 넘어 버린다면?
  • 데이터 품질이나 데이터 리니지 이슈 이외에도 다양한 이슈들이 발생한다.
    • 라이브러리 충돌 (버전 충돌)
    • Worker의 부족
      • 처음에는 single node로 돌리기 때문에 서버 한 대에서 돌게 되지만 DAG가 추가됨에 따라 node 하나로는 부족해진다. 그렇기 때문에 node를 추가해야 하고, single node에서 multi node cluster로 바뀌게 된다. 이 과정에서 운영상의 복잡함이 발생하게 된다.
    • 다수의 서버를 사용하게 되면 놀고 있는 서버들로 인한 자원 낭비가 있을 수 있고, airflow와 웹서버, 데이터베이스도 전용 서버를 각자 할당하게 되면 관리하는 게 힘들어지고, utilization이 낮아진다.

1) 라이브러리 충돌

  • 라이브러리/모듈의 충돌 이슈가 발생한다.
  • DAG에 따라 실행에 필요한 라이브러리와 모듈이 달라지기 시작한다.
  • 이로 인해 DAG 혹은 태스크 별로 별도의 독립 공간을 만들어 주는 것이 필요하다.
    • Dag 혹은 Task 코드를 Docker Image로 만들고 이를 독립된 공간 (Docker Container) 안에서 실행

2) Worker 부족

  • Worker가 부족하면 Scale Up, Scale Out, K8s와 같은 컨테이너 기술이 필요하다.
  • Scale Up 하더라도 일정 시점이 되면 Scale Out이 필요하다. 이 과정에서 클라우드 서비스를 사용하는 것을 고려해 보아야 한다.
  • K8s어떤 역할이 명확하게 정해지지는 않았지만 언제든지 사용할 수 있는 공용 서버 클러스터이다.

3) 낮은 Server Utilization 이슈

  • Airflow 전용 하드웨어를 지정했는데 서버들이 항상 바쁘지 않다면?
  • 서비스별로 전용 서버를 할당하는 것은
    • 서비스별로 capacity 관리를 해야 함
    • 각 서비스에 속한 서버들은 utilization이 낮은 이슈가 발생함
  • 이 문제는 K8s로 해결할 수 있다. K8s는 온디맨드로 필요할 때 서버를 사용하고 그 서버의 사용이 끝나면 사용을 종료할 수 있기 때문에 불필요한 서버 사용이 일어나지 않는다.

2. 해결책

  • 태스크나 DAG 코드를 Docker Image로 만들어서 Docker Container 형태로 실행한다. 이렇게 되면 라이브러리/모듈 충돌을 방지할 수 있고, 개발 환경과 프로덕션 환경을 동일하게 유지할 수 있다.

  • Airflow Worker를 K8s에서 필요한 대로 동적으로 할당해서 사용한다. 전용 서버를 Airflow에 할당하지 않고 Container Orchestration 서비스를 통해 할당해서 사용하고 리턴한다.

  • Airflow에서 이를 해결하는 방법은?

    • Airflow Operator로 KubernetesPodOperator 사용
    • Airflow Operator로 DockerOperator를 사용
      -> Operator를 사용하고 있기 때문에 여전히 Airflow 전용 서버를 사용
    • Airflow Executo로 아래를 사용
      • KubernetesExecutor
      • CeleryKubernetesExecutor
      • LocalKubernetesExecutor

✍ Airflow Executor는 무엇인가?

  • Executor는 Task들을 관리하고 실행하는 역할을 수행한다.
    • 이 타입에 따라 병렬 혹은 일렬 실행이나 어떤 worker로 실행할지 결정됨
  • 다양한 수의 Executor 타입이 존재한다.
    • Sequential Executor: default. sqlite와 같은 싱글 스레드 DB에서만 사용 가능하다.
    • Local Executor: task들을 Airflow 마스터 노드안에서 실행
    • Celery Executor: 다수의 Worker 노드가 있는 경우 사용되며 Celery 큐를 사용해 task들을 worker 노드로 분산하여 실행
    • Kubernetes Executor는 K8s 클러스터를 사용하여 task들을 독립된 환경에서 사용
    • Local Kubernetes ExecutorCelery Kubernetes Executor도 존재

3. Docker를 사용하는 이유

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

    • 설치 과정에서 중요한 파일이 빠지거나

    • 사용하는 라이브러리의 버전이 안 맞거나

    • 환경 설정이 안 맞을 때

    • 이때 가장 쉬운 해결 방법이 Docker이기 때문에 Docker 사용

  • 왜 Docker의 사용이 가장 쉬운 해결 방법인가?

    • 내 컴퓨터 환경을 그대로 패키징해서 다른 이에게 줄 수 있기 때문에 내가 만든 프로그램이 다른 컴퓨터에서도 모두 돌아갈 수 있다.

✍ Docker Image와 Docker Container

  • Docker Image: 독립적으로 완전하게 만들어진 패키지를 말한다.
  • Docker Container: 이 Docker Image를 독립된 환경에서 실행한 것이다.

4. Virtual Machine vs Docker Container

  • Virtual MachineHost 운영 체제가 존재하고, 그 위에 VM을 생성하고 관리하는 소프트웨어가 있어야 한다. Hypervisors, VMWare, Virtual Box 등.
  • 그리고 그 위에 다수의 Virtual Machine이 올라갈 수 있는데 VM들은 자체 OS를 가지고 올라가야 한다.
  • 즉, 컴퓨터 하드웨어를 추상화해서 하드웨어 안에 또 다른 하드웨어를 돌리는 방식으로 운영된다.
  • Virtual Machine의 장점
    • 소프트웨어를 실행하기 위해 독립적이고 분리된 공간 제공
    • 다수의 소프트웨어를 각 VM 단에서 독립적으로 실행 가능
  • Virtual Machine의 단점
    • 각 VM은 자신만의 OS가 필요하기 때문에 유료 OS면 라이센스가 필요하고 시작하는 데 시간이 오래 걸림
    • 자원을 많이 사용해야 함 (VM끼리 자원을 나눠 써야 함)
  • Docker Container는 소프트웨어를 실행하기 위한 독립적이고 분리된 공간이다.
  • Volume이라는 자체 파일 시스템을 가지고 있다. Host OS에 따라 지원하는 OS가 정해져 있다.
  • Container의 장점
    • 소프트웨어를 실행하기 위해 독립적이고 분리된 공간을 제공, 다수의 소프트웨어를 각 컨테이너에서 독립적 실행 가능
    • 자원 소비가 적음
    • 호스트 OS를 사용하여 별도 비용이 없고 빠르게 실행됨
  • Container 단점
    • 많은 수의 Docker Container를 관리하는 것은 쉽지 않음
    • Host OS를 사용하기 때문에 Host OS가 지원하는 OS만 지원을 해 줌, 즉, Cross-Platform compatibility를 항상 지원하지는 않음
    • GUI 소프트 개발에 적합하지 않음

5. 하이레벨 Docker 사용 프로세스

  • 먼저 대상 소프트웨어를 선택해야 한다.
  • 다수의 컴포넌트로 구성되는 소프트웨어라면 각각 Docker Image로 만들어져야 할 수도 있다.
  • 이를 Docker Image로 빌드하는 것을 Dockerization이라고 부른다.
  • Dockerfile이란 텍스트 파일로 소프트웨어를 이미지로 바꾸기 위해 Docker에게 주는 명령문들이 기술되어 있다.
  • Docker Image는 하나의 Docker container 안에서 실행되며 Dockerfile을 기준으로 만들어진다.
  • docker build -t tag(이미지 이름과 버전 지정)
  • Docker 명령어 종류
    • FROM: 뭘 기반으로 시작되었는지. (Python, Linux 등)
    • RUN: 어떤 프로그램을 설치할 것인지.
    • ENTRYPOINT, CMD: 프로그램을 실행.

6. Docker 구성

1) Docker Image의 구성 요소

  • 기본 OS와 같은 소프트웨어 실행 환경
  • 소프트웨어 자체 (코드)
  • 코드가 돌기 위해 필요한 각종 라이브러리
  • 파일 시스템 스냅샷 (스택화된 형태로 구현)
  • 환경 설정 변수 두 가지 (빌드할 때 변수와 실행할 때 변수로 ARG, ENV)
  • 메타 데이터: 이미지에 대한 정보 (버전, 작성자, 설명 등) -> 필수는 아님

2) Docker Image의 실행

  • Container를 통해 Docker Image 안의 소프트웨어를 실행한다.
  • Container 자체 파일 시스템을 가진 특수한 프로세스로 이미지의 파일 시스템이 로딩된다.
  • Image를 Container 안에서 실행한다. docker run

3) Docker Image 등록

  • Docker Registry는 Docker Image들의 보관소로 생성된 Docker Image가 등록 시 공유되기 때문에 회사 내 혹은 퍼블릭하게 이미지 공유를 원할 시 사용한다.
  • On-prem registry와 Cloud registry가 존재한다.
  • docker hub가 가장 유명하다. 🔗 docker hub

4) Docker Hub

  • Docker가 제공해 주는 서비스로 Docker Image를 공유하고 찾기 위한 서비스이다.
  • Public과 Private Repo를 제공한다.
  • Teams & Organizations 형태로 큰 회사라면 Organizations, 작은 회사라면 Teams.
  • Github과 밀접하게 연동된다.

7. Hello World 프로그램 실습

1) Dockerfile 생성

  • Docker에게 소프트웨어 설치 명령을 기술
  • 먼저 베이스 이미지를 기술 (FROM)
  • 다음으로 COPY를 통해 코드 복사
  • 마지막으로 코드 실행 (CMD)
FROM node:alpine  #OS 종류를 적어 준다. Alpine은 경량 리눅스이고 데모 목적
COPY ./app # 코드 복사에 사용
WORKDIR /app # Working directory를 지정 
CMD node app.js # 실행하는 명령 앞에 지정

2) Dockerfile 사용 가능 키워드

  • ARG
    • Docker Image를 만들 때 사용되는 변수 지정
    • 최종 이미지에는 들어가지 않음
  • ENV
    • 컨테이너가 실행될 때 사용되는 환경 변수
    • 최종 이미지에 저장됨
  • USER
    • 컨테이너를 실행할 때 사용할 USER ID
  • EXPOSE
    • 서비스 사용 포트 번호
  • RUN
    • 빌드 시 실행되어야 하는 명령들이 지정됨 (docker build)
    • RUN apt-get update && apt-get install -y curl (RUN 뒤에는 다양한 명령이 올 수 있다. 해당 명령도 &&을 사용해 두 개의 명령을 지정한 것이다.)

3) CMD vs ENTRYPOINT

  • Container가 시작할 때 실행되어야 하는 명령어를 지정할 때 사용하고
  • docker run 명령을 쓰고 뒤에 docker image를 적어 docker image를 실행할 때 어떤 명령어를 실행할 것인지 적어 준다.
  • CMDENTRYPOINT를 두 번 쓰면 마지막 명령만 실행된다.
CMD ["command1.sh"]
CMD ["command2.sh"]
#이런 경우 command2.sh만 실행됨
FROM debian:buster
COPY . /myproject
RUN apt-get update …
CMD ["./cmd1.sh"]
  • 또한 CMDoverriding이 가능하기 때문에 그냥 docker run my-image를 하면 cmd1.sh가 기본으로 실행되지만 docekr run my-image cmd2.sh를 실행하면 cmd2.sh가 실행된다.
  • 두 개가 같이 쓰일 경우 ENTRYPOINT가 우선 순위가 되는데 Best는 ENTRYPOINT는 최대한 쓰지 않고 CMD만 쓰는 게 일반적이다.
FROM debian:buster
COPY . /myproject
RUN apt-get update …
ENTRYPOINT ["entrypoint1.sh"]
CMD ["param1", "param2"]
  • 같이 쓰인다면 ENTRYPOINT가 명령이 되고 CMD가 파라미터를 제공하는 형식으로 쓰인다.
#output
docker run my-image
=> entrypoint.sh param1 param2
  • 앞서 말했듯 CMD는 overriding이 가능하고, ENTRYPOINT는 --entrypoint 옵션을 통해서면 overriding이 가능하다.
docker run my-image cmd2
=> entrypoint.sh cmd2 #output
  
docker run --entrypoint="/cmd3.sh” my-image  

4) Docker Registry에 등록

  • docker image ls
  • docker tag hello-world-docker:latest dockerhub-repo:latest -> 현재 이미지의 repo 이름을 dockerhub repo로 별칭을 만들어 줌 (이름을 바꾸는 게 아니라 별칭)
  • docker image ls
  • docker login --username=dockerhub
    -> 이때 패스워드를 별도의 프롬프트에서 입력해 주어야 함.
  • docker push dockerhub
  • 이렇게 세팅을 해 주게 되면 dockerhub에서 만든 형태로 image 이름을 바꿔 주고, namespace에 해당 repo로 접근 권한이 있다는 것을 인증하고, push를 통해 업로드를 하는 것이다.

  • push까지 했다면 다음과 같이 Docker HubDocker Image가 올라간 것을 볼 수 있다.

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

  • https://labs.play-with-docker.com/를 사용해 4 시간 동안 서버 무료 사용
  • Docker Hub에 로그인이 되어 있다면 계정 연결을 허용해 준다.
  • Window를 최대화(전체 화면)하기 위해서는 Alt+Enter (Mac은 Option+Enter) 이후 아래 명령어를 실행한다.
    • docker version (docker 버전 확인)
    • docker pull dockerhub-repo (접근 권한이 있다면 image가 다운로드가 될 것)
    • docker image ls
    • docker run dockerhub-repo

📌 빌드 시 오류 발생

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
이런 오류를 막기 위해 이미지 생성 시 --platform을 통해 platform을 지정해 주어야 한다. $ docker build --platform linux/amd64 -t hello-world-docker .


6) docker run vs docker exec

  • docker run새로 Container를 실행하는 것이다.
  • docker exec실행된 Container에 작업하는 것이라 Container ID가 꼭 필요하다.
  • 두 명령 모두 --user root 혹은 -u root를 통해 루트 유저로 연결 가능하다.

7) Docker Tag란?

  • Docker Image의 버전이나 변형을 나타내는 문자열
  • default는 latest
  • Docker Image의 부가 정보를 나타냄
  • 지정해 줄 때는 Docker Image 이름 : 뒤에 붙여 줌

8) Docker Image 이름

  • docker image ls에서 Repo에 해당된다.
  • Docker Hub에서 다운로드 받은 것이라면 AccountID (Namespace)를 포함할 수 있지만 공식 이미지는 Namespace가 없다.
  • 포맷은 Repo이름:태그

8. Ubuntu 환경 실습

  • docker run ubuntu
    • 현재 ubuntu 이미지가 존재하지 않기 때문에 Docker Hub에서 ubuntu 이미지를 가지고 와 준다.
    • 이때 docker ps를 쓰면 ubuntuContainer가 보이지 않지만 docker ps -a를 쓰면 실행이 끝났던 Container도 보이게 된다.
  • docker run -it ubuntu
    • ubuntu는 그냥 시작하면 바로 끝나게 된다.
    • 그래서 해당 명령어를 입력해 주는데 그러면 다음과 같이 창이 뜬다. 이때 nano를 입력해 주고, nano가 설치되지 않았다면 오류가 발생한다. apt install nano로 다시 설치를 해 주는데 다음과 같은 오류가 또 발생한다면 update를 해 주어야 한다.
    • apt update를 통해 update를 해 준다. 다양한 소프트웨어를 어디서 받을 수 있는지를 업데이트해 주는 것이다.
    • 이후 다시 apt install nano를 설치하면 제대로 설치가 된다. 이때 nano는 편집기이다.
    • nano를 다 사용했다면 apt remove nano를 해 주면 삭제된다.

9. MySQL 서버 실행

  • docker pull mysql/mysql-server:8.0
    • Docker Engine이 실행되면 해당 명령어를 통해 MySQL docker image를 다운로드한다.
  • docker run --name=mysql_container mysql/mysql-server:8.0
    • 다운로드 받은 이미지로 Docker Container를 실행한다.
    • --name=은 Docker Container의 아이디를 주어 ps로 아이디를 찾는 과정을 스킵할 수 있게 해 준다.
  • docker logs mysql_container 2>&1 | grep GENERATED
    • MySQL root 계정의 패스워드를 찾아 준다.
    • logs 메시지를 읽는 것이다.
  • docker exec -it mysql_container mysql -uroot -p
    • MySQL shell을 실행하고 root 계정으로 로그인하겠다는 의미이고 password 입력 시 앞서 log로 찾은 password를 입력해 주면 된다.

🔎 어려웠던 내용 & 새로 알게 된 내용

1. ENTRYPOINT를 사용하면 더 편해지는 경우

  • 강의에서는 ENTRYPOINT를 꼭 사용해야 하는 경우는 존재하지 않고 최대한 CMD만 사용하라고 했지만 ENTRYPOINT를 사용하면 더 편해지는 경우가 존재한다고 했다. 그 부분이 궁금해서 조금 더 학습해 보기로 하였다.
  • 인수 고정
    • ENTRYPOINT를 사용하면 Dockerfile에서 컨테이너에 대해 항상 실행되는 기본 실행 명령을 지정할 수 있다.
    • 예를 들어, ENTRYPOINT ["java", "-jar", "myapp.jar"]와 같이 ENTRYPOINT를 설정하면 컨테이너가 항상 java -jar myapp.jar를 실행한다.
    • 컨테이너 실행 시에 추가 인수를 제공해도 ENTRYPOINT에서 정의한 명령은 변경되지 않고, ENTRYPOINT는 고정된 부분을 유지하며 컨테이너의 기본 실행 명령을 지정한다.
    • 하지만 CMD 명령을 사용하면 default로 설정해 둔 명령이 overriding 된다.
  • 컨테이너 설정 초기화
    • ENTRYPOINT는 컨테이너가 시작될 때 설정을 초기화하는 데 사용된다.
    • 예를 들어, 환경 변수, 파일 복사, 네트워크 구성 등 초기화 작업을 ENTRYPOINT 스크립트에 포함시킬 수 있다.
    • 이를 통해 컨테이너가 시작될 때마다 일관된 설정을 보장할 수 있다.
  • 정리해 보면 명령어가 먼저 default 값을 설정하지만 상황에 따라 명령어가 달라질 여지가 있을 때는 CMD를 그렇지 않고 변경이 필요 없거나 꼭 실행되어야 하는 명령어일 경우 ENTRYPOINT를 사용한다.

2. grep 명령어 Window에서는 어떻게 사용해야 할까?

  • 실습 과정에서 docker logs mysql_container 2>&1 | grep GENERATED 해당 명령어를 통해 log에서 GENERATED라는 문장을 찾는 부분이 있었다.
  • 입력하니 'grep'은(는) 내부 또는 외부 명령, 실행할 수 있는 프로그램, 또는 배치 파일이 아닙니다. 다음과 같은 오류가 발생했고 확인해 보니 window에서는 grep 명령어가 존재하지 않았다.
  • 대신 써 줄 수 있는 명령어는 findstr였다.
  • findstr는 뒤에 무엇이 붙느냐에 따라 명령어가 조금 달라지게 되는데 기본적으로는 파일에서 문자열을 찾는 명령어이다.
  • docker logs mysql_container 2>&1 | findstr GENERATED 해 주니 원하는 결과인 GENERATED ROOT PASSWORD: password가 나왔다.


✍ 회고

docker를 공부할수록 앞으로 데이터 엔지니어의 업무에서 docker도 뗄 수 없는 환경이 되지 않을까라는 생각이 들었다. DevOps와 MLOps에 이어 dataOps라는 직군이 생기게 되면 대부분 docker를 사용하게 될 것 같다는 생각이 들었다. 아무튼 공부해야 할 게 참 많은 것 같다.

profile
송의 개발 LOG

0개의 댓글