Docker와 Container (TIL 36)

석형원·2024년 5월 30일

TIL

목록 보기
36/52

✏️ 오늘 학습한 내용

1. Airflow 운영 상의 어려움
2. Docker 소개
3. Virtual Machines vs. Docker
4. Docker 프로그램 개발 프로세스
5. Docker로 프로그램 제작
6. Container로 우분투 실행
7. Root 계정으로 MySQL 이미지 실행


🔎 Airflow 운영 상의 어려움

📃 DAG의 수가 많아지면 발생하는 이슈

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

    • 라이브러리 충돌
      ( DAG들의 버전 충돌 )

    • Worker의 부족

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

라이브러리 충돌

  • 라이브러리/모듈의 충돌 이슈가 발생

  • DAG에 따라 실행에 필요한 라이브러리/모듈이 달라지기 시작
    (Python 버전)

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

    • Docker to the rescue

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

Worker의 부족

  • Scale Up, Scale Out 이슈

  • K8s(공용 서버 클러스터)와 같은 컨테이너 기술 사용

    • 필요한대로 서버 요청

    Airflow와 쿠버네티스를 연결해서
    DAG를 실행할 때마다 자체 할당된 Worker를 사용하는 것이 아니라
    쿠버네티스의 서버를 요청하고 받아서 실행하는 방법

낮은 Server Utilization 이슈

  • Airflow 전용 하드웨어를 지정했음에도 서버들이 항상 바쁘지 않다면?

    • 특정 시간에 놀고 있는 서버들이 존재
  • 서비스 별로 전용 서버를 할당하는 것은 여러가지로 이슈를 만들어냄

    • 서비스 별로 Capacity 관리가 필요

    • utilization이 낮은 이슈가 발생

  • K8s와 같은 컨테이너 기술의 도입으로 해결

    쿠버네티스 서버 클러스터를 충분하게 만들어두고 각 서비스가 필요할 때마다 on demand 형태로 가져다가 쓰고 불필요한 경우 return하는 방식으로 간다면 Capacity 관리가 편해질 것이고 Utilization도 올라갈 것입니다.

해결책

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

    • 개발 환경과 프로덕션 환경을 동일하게 유지
      -> 충돌 방지
  • Airflow Worker를 K8s에 필요한 대로 동적으로 할당하여 사용

    • 전용 서버를 Airflow에 할당하지 않고 Container Orchestration 서비스를 통해 할당해서 사용 후 리턴
  • Airflow에서 이 해결책을 사용하는 방법 3가지

    • DockerOperator 사용

      Task를 Docker Image로 만들어서 Airflow worker 위에 동작하는 Docker 컨테이너에서 실행,

      환경이 충돌이 나는 DAG의 Task들에 한해서만 DockerOperator를 적용하여 해결하는 방법도 가능

    • KubernetesPodOperator 사용

      가장 Scalable한 방법
      Task 자체를 Docker Image로 만들어서 쿠버네티스에서 서버를 받아와 실행

    • 아래 Airflow Executor를 사용
      ( 쿠버네티스 내의 서버를 사용할 수 있는 Executor )

      • KubernetesExecutor

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

        • Celery와 Kubernetes Executor를 동시에 사용하는 방법을 제공

        • Airflow 로드가 전체적으로 큰데 소수의 Task만 Isolation을 필요로 하는 경우 사용!

      • LocalKubernetesExecutor

        • Local과 Kubernetes Executor를 동시에 사용하는 방법을 제공

Airflow Executor란?

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

    • 병렬 혹은 일렬 실행이나 어느 worker에서 실행할지 등등
      ( Executor의 Type에 영향을 받음 )
  • 다양한 수의 Excutor 타입이 존재

    • Sequential Executor : Default로 설치, Sqlite와 같은 싱글스레드 DB에서만 사용가능

    • Local Executor : Task들을 Airflow 마스터 노드안에서 실행
      ( Worker 노드가 마스터 노드 하나 밖에 없음, Airflow가 노드 하나짜리 클러스터인 경우 사용 )

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

    • Kubernetes Executor : K8s 클러스터를 사용하여 task들을 독립된 환경에서 사용 ( 모든 Task는 Docker Image가 되어야함 )

    • Local, Celery Kubernetes Executor :
      Local 혹은 Celery Excutor와 Kubernetes Executor 두 개를 동시에 지원함


🔎 Docker 소개

내가 만든 프로그램이 다른 컴퓨터에서 안돌아가는 현상은 종종 겪을 수 있습니다. 컴퓨팅 환경이 다르기에 환경 설정이 안맞을 수도 있고 중요한 파일이 빠졌을 수도 있죠.

이러한 점들을 해결하기 위해 컴퓨팅 환경을 그대로 패키징해서 다른 이에게 줄 수 있으면 어떨까? 라고 해서 나온 것이 Docker입니다.

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

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

Docker의 목표

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

🔎 Virtual Machines vs. Docker Containers

📃 Virtual Machine

Virtual Machine 이란

  • AWS의 EC2가 대표적인 Virtual Machine(VM)

  • 하드웨어를 추상화하여 한 컴퓨터 위에 가상 컴퓨터를 올림

    • 컴퓨터 하드웨어 단의 추상화

Docker 컨테이너와 차이점

  • Host 운영체제가 있고 ex) Macintosh

  • Host 운영체제 위에 Hypervisors라는 VM을 생성하고 관리하는 Layer가 있고

  • 그 위에 다수의 VM이 올라갈 수 있음
    ( 각 VM은 자체 OS를 가지고 올라가야함 ex) linux )

  • 결국 하드웨어 안에 또 다른 하드웨어가 있는 형태
    ( 장점도 있지만 치명적인 단점이 존재 )

Virtual Machine의 장단점

  • 장점

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

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

  • 단점

    • VM은 자신만의 OS를 필요로 함

      • 유료 OS라면 라이센스 비용 필요
      • 시작하는데 오래 걸림 (속도 느림)
    • 자원을 많이 사용함
      ( VM들끼리 자원을 나눠써야함 )

📃 Docker

Docker Container 소개


Docker System 자체가 자기만의 무거운 OS를 가지고 있는 것이 아니고,
어떤 서버와 하드웨어 위에 호스트 운영체제가 있고,
그 위에 Docker Engine만 동작한다면.

경량화된 형태로 자기만의 분리된 공간을 갖는 Container를 동작시킬 수 있습니다.

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

  • 자체 파일 시스템을 보유 (Volume)

컨테이너 OS가 리눅스인 경우 모든 OS에서 동작시킬 수 있지만. 윈도우인 경우는 윈도우에서만 동작시킬 수 있습니다.

  • Docker Container의 단점

    Command Line Application을 만들기는 좋지만 윈도우 APP 등을 만들고 동작시키기에는 제약이 많습니다.

Docker Container 장단점

  • 장점

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

      • 다수의 소프트웨어를 각 컨테이너 단에서 독립적으로 실행 가능
    • 자원 소비가 적음 (lightweight)

      • 수 십, 수 백개의 container 실행 가능
    • 호스트 OS를 사용 (추가 비용 없음)

      • 실행이 빠름
  • 단점

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

    • Host OS를 사용하기에 Cross-platform compatibility가 항상 지원되는 것은 아님

    • GUI 소프트웨어 개발에 적합하지 않음

VM vs. Containers 비교

  • VM

    • 자원소모가 큼 (Utilization)
    • 메모리 공간도 많이 차지(Size)
    • 부팅 시간도 오래걸림
    • Docker에 비해 실행할 수 있는 수가 적음
    • 자체 OS를 보유 (Guest OS)
  • Docker

    • 모든게 가볍고 (Utilization, Size)
    • 부팅 시간도 빠름
    • Docker Engine만 있으면 별도의 운영체제 없이 동작함

🔎 Docker 프로그램 개발 프로세스

하이레벨 Docker 사용 프로세스

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

    • 다수의 컴포넌트로 구성되는 소프트웨어의 경우 각각을 Docker Image로 만들어야할 수도 있습니다.
  • Docker Image로 빌드 (Dockerization)

    • Dockerfile이란 텍스트 파일을 통해 세부 정보를 기술

      • 이미지를 빌드하기 위한 Docker에게 주는 명령들이 Dockerfile에 포함됨
    • Docker Image

      • 하나의 Docker Container 내에서 실행됨
      • Dockerfile을 기준으로 만들어지며 소프트웨어를 실행하기위해 필요한 모든 것을 포함

Docekr Image의 구성 요소

  • 기본 OS와 같은 소프트웨어의 실행환경

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

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

  • 파일 시스템 스냅샷 : 스택화된 형태로 구현

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

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

위 정보와 설치 관련 실행 순서들이 Dockerfile에 기술되고,
Docker Image는 다수의 파일로 구성됩니다.
-> docker image ls

Docker Image 실행

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

    • Container는 자체 파일 시스템을 가진 특수한 프로세스로 이미지의 파일 시스템이 로딩됩니다.
  • 실행 명령어

    • docker run ...

Docker Hub

Docker가 제공해주는 서비스로,
Docker Image를 공유하고 찾기 위한 서비스
https://hub.docker.com/

  • Docker Registry

    • Docker Image들의 보관소
    • On-premise registry와 Cloud registry가 존재
    • docker hub가 가장 유명
  • Docker Hub에 등록을 하면 프라이빗 혹은 퍼블릭하게 이미지를 공유가 가능

  • Github와 연동을 통한 Automated Build 기능 제공

  • Teams & Organizations 제공


🔎 Docker로 프로그램 제작

프로그램 개요

  • Node.js로 구성된 초간단 웹 서비스
    • app.js 하나가 전부
    • node 런타임 환경이 필요
  • 실행 명령어
    node app.js

Docker 없이 프로그램을 실행하는 순서

  1. OS 선택
  2. Node 설치
  3. 코드 복사
  4. 프로그램 실행 node app.js

-> 이 내용을 Dockerfile에 기술하면,
Docker Image 생성이 가능!

📃 Dockerfile의 생성

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

  • 베이스 이미지를 기술(FROM)

  • 코드 복사

  • Dockerfile 코드

    # OS 종류, Alpine이라는 경량 리눅스를 선택
    FROM node:alpine
    # 현재 로컬의 모든 내용을 이미지 내부의 /app에 복사
    COPY . /app
    # Working dir를 지정
    WORKDIR /app
    # WORKDIR를 지정한 이후로 현재 경로가 WORKDIR로 변경됨
    # 실행이 되어야하는 명령어를 지정
    CMD node app.js
  • app.js 코드

    console.log("Hello Docker!");

Dockerfile 사용 가능 기타 키워드

  • ARG :

    • Docker Image를 만들 때만 사용되는 변수 지정.
    • 최종 이미지에는 포함되지 않음
  • ENV :

    • 컨테이너가 실행될 때 사용되는 환경변수
    • 최종 이미지에도 포함이 되는 환경 변수
  • USER

    • 컨테이너를 실행할 때 사용하는 유저 ID
      ex) 리눅스 user_id
  • EXPOSE

    • 서비스 사용 포트번호
  • RUN

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

    • e.g.) apt-get 업데이트 후 curl 설치
      RUN apt-get update && apt-get install -y curl

CMD vs. ENTRYPOINT (1)

  • Container가 시작할 때 실행되어야 하는 명령어를 지정하는데 사용

    • 굉장히 흡사한 기능을 제공하지만 우선 순위가 존재

      동시에 쓰였을 경우 ENTRYPOINT가 우선 순위가 높습니다.
      그러나 Best Practice는 ENTRYPOINT를 사용하지 않고 CMD만 사용하는 것입니다.

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

    • 아래의 경우, 동일한 결과가 나옵니다.
  • container가 실행될 때 실행됩니다.
    docker run
    e.g.)

    # my-image
    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가 실행됩니다.
    (CMD의 명령어가 Override 되었기 때문)

CMD vs. ENTRYPOINT (2)

  • CMD, ENTRYPOINT 둘 다 한 Dockerfile에서 같이 지정가능함

  • 둘이 같이 사용되는 경우, ENTRYPOINT가 기본 명령이되고 CMD가 인자를 제공하는 역할

  • ENTRYPOINT는 --entrypoint 옵션을 통해서만 override가 가능

FROM debian:buster

COPY ./myproject
RUN apt-get update
ENTRYPOINT ["entrypoint.sh"]
CMD ["param1","param2"]

docker run my-image
-> entrypoint.sh param1 param2

docker run my-image cmd2
-> entrypoint.sh cmd2
( cmd2가 기본 명령에 override가 되지않고 인자에 override되어 들어갑니다. )

docker run --entrypoint="/cmd3.sh" my-image
-> /cmd3.sh
(--entrypoint를 통해 기본 명령도 override할 수 있습니다.)

CMD vs. ENTRYPOINT (정리)

  • 최대한 CMD만 사용하는 것이 좋음

  • ENTRYPOINT를 사용하면 파라미터를 지정하기 때문에 실행시 타이핑이 덜 할 수 있음

    • 파라미터가 감춰진다는 점으로 인해 오히려 혼란을 줄 여지가 있습니다.

추가 예시)

FROM python:3.7-slim-buster

# 최종 이미지까지 남아있는 환경 변수
ENV DEBIAN_FRONTEND noninteractive

# 빌드 내에서만 사용 가능한 환경 변수
ARG AIRFLOW_VERSION=1.10.9
ARG AIRFLOW_USER_HOME=/usr/local/airflow

# local경로에서 -> docker image 안으로 복사
COPY config/airflow.cfg ${AIRFLOW_USER_HOME}/airflow.cfg

# user : airflow로 변경하여 실행
RUN chown -R airflow: ${AIRFLOW_USER_HOME}

# 포트번호
EXPOSE 8080 5555 8793

# airflow라는 user로 들어오게 해라
USER airflow
# 기본 폴더를 ~로 지정
WORKDIR ${AIRFLOW_USER_HOME}
ENTRYPOINT ["/entrypoint.sh"]
CMD ["webserver"]
# /entrypoint.sh webserver가 실행

📃 Docker Image 생성

docker build를 실행하면 Dockerfile에서 RUN 명령이 자동으로 실행됩니다.

예시)
docker build --platform linux/amd64 -t hello-world-docker .

  • --platform linux/amd64

    Apple M1 chip 기반의 Mac에서 빌드하는 경우 이미지가 ARM 기반 아키텍쳐로 만들어지기 때문에 일반 리눅스에서는 돌아가지 않습니다.
    그렇기에 linux/amd64로 지정합니다.

  • -t hello-world-docker

    -t는 태그 지정 명령어,
    이미지 이름을 hello-world-docker로 지정

  • .

    현재 디렉토리의 dockerfile을 기반으로 이미지를 생성한다는 의미로 사용합니다.

    다른 디렉토리의 dockerfile을 사용하는 경우에는 .이 아닌 그 경로를 지정해줍니다.

Docker Image 확인

docker image ls 명령어를 통해 확인
REPOSITORY : 이미지 이름
TAG : 버젼 (지정이 없으면 latest)

Docker Container로 실행

docker run hello-world-docker
-> image에 해당하는 CMD에 지정된 명령이 실행됩니다. "Hello World!"

  • 만일 이 이미지를 다른 컴퓨터에서 실행하고자 한다면
    -> Docker Registry에 등록 후 다른 컴퓨터에서 받아오기

📃 Docker Hub에 등록

https://hub.docker.com/ 에서 회원 등록
( Docker Desktop으로도 가능 )

  • repository 생성
    Repositories -> Create repository
    ( Public 속성 지정 )

  • 터미널에서 명령을 실행

docker image ls
# 지금 local에 존재하는 이미지의 이름을 
# docker hub에서 지정한 이름과 동일하게 변경
docker tag hello-world-docker:latest skqltldnjf77/hello-world-docker:latest

docker image ls

# docker 로그인
docker login --username=skqltldnjf77

# docker hub에 업로드
docker push skqltldnjf77/hello-world-docker

📃 Docker Hub로 받은 Image 실행

Docker는 4시간 동안 서버를 하나 무료로 사용할 수 있는 서비스를 제공해주고 있습니다.

이 서버에서 이미지를 다운받아 컨테이너로 실행해보겠습니다.

https://labs.play-with-docker.com/

  • ADD NEW INSTANCE
    • 인스턴스를 하나 추가해주고 여기서 터미널 창에서 명령을 실행합니다.

터미널의 크기가 작다면,
윈도우는 Alt+Enter
맥은 Option+Enter으로 화면을 최대화시킬 수 있습니다.

docker version
# docker hub에서 이미지를 내려받음
docker pull skqltldnjf77/hello-world-docker
docker image ls

# pull없이 단독으로 run을 사용해도
# 만일 로컬에서 다운로드받은 이미지가 없다면
# docker hub에서 pull을 알아서 수행합니다.
docker run skqltldnjf77/hello-world-docker

  • 에러 발생 시

    만약 위 에러가 발생한다면
    위에서 진행했던 build시 --platform옵션을 통해 linux/amd64로 변경해주는 작업이 필요

  • docker run

    • p option (port mapping) :
      port 번호를 변경하고 싶은 경우
    • v option (volume mapping) :
      volume 옵션을 변경하고 싶은 경우

docker run vs. docker exec

  • docker run

    • 지정된 이미지를 새로운 Container에서 실행
  • docker exec

    • 이미 실행된 Container에 대해서 작업을 하는 것
      • Container ID가 필요
  • 두 명령 모두 --user root 혹은 -u root를 통해 루트 유저로 연결 가능

Docker tag란

  • Docker Image의 버전이나 변형을 나타내는 문자열
    • default : latest
    • Docker Image의 부가정보를 나타냄
  • Docker Image 이름의 ':'이후 부분에 해당
    • ex)
      ubuntu:18.04
      hello-world-docker:latest

🔎 Container로 우분투 실행

리눅스 커널과 배포판

리눅스 커널 : 리눅스의 핵심 부분

리눅스 배포판 : 핵심 부분은 동일하지만 그 앞의 패키징이 달라지는 것

리눅스 배포판의 종류

  • 우분투 : 가장 많이 사용되며 데비안 기반해서 만들어진 리눅스 배포판

  • 데비안

  • 알파인 : 임베디드 시스템에서 사용할 용도로 만들어진 경량화 리눅스 배포판

  • 페도라

우분투 설치 후 명령 실행

# docker hub에서 ubuntu를 설치
# 사실 컨테이너도 같이 실행이 된 것이지만
# 별다른 명령이 없었기에 바로 종료되는 것
docker run ubuntu

docker ps
# -a 명령어로 실행이 끝난 컨테이너도 확인 가능
docker ps -a

# -it interactive 옵션을 통해
# ubuntu 아이디로 로그인해서 다양한 명령 실행
docker run -it ubuntu
# apt update, apt install nano, ...


🔎 Root 계정으로 MySQL 이미지 실행

MySQL 서버에 root 계정으로 로그인을 하려면,
MySQL 서버가 임시로 만들어 둔 Password를 읽어와야합니다.

그 내용이 이미지 설치 시에 표기가 되는데,
이는 Container 내부 로그로 남아있습니다.

docker logs 명령어를 통해 "2>&1"로 표준 에러를 표준 출력으로 리디렉션 후,
"GENERATED"라는 키워드가 들어간 라인을 찾으면, 그 뒤에 적혀있는 패스워드를 찾을 수 있습니다.

  • "2>&1"
    • '1' : 표준 출력 (standard output)
    • '2' : 표준 에러 (standard error)
    • '>' : redirection
      ( 왼쪽의 결과를 오른쪽으로 리디렉션 )

실행 코드

# docker hub에서 MySQL을 설치
docker pull mysql/mysql-server:8.0

# --name 명령으로 실행 할 container에 이름을 부여
# 이 경우 container_id를 별도로 찾을 필요 없이 
# 지정한 이름을 id처럼 사용할 수 있습니다.
docker run --name==mysql_container mysql/mysql-server:8.0

# 이 터미널은 mysql 서버가 돌아가기 때문에
# 터미널을 하나 새로 생성

# pw 찾기
docker logs mysql_container 2>&1 | grep GENERATED

# root
계정으로 MySQL shell 실행
# exec는 이미 실행된 컨테이너를 대상으로 실행!
docker exec -it mysql_container mysql -uroot -p

MySQL에서 임시비밀번호를 변경

# 임시비밀번호를 원하는 비밀번호로 변경해줍니다.
ALTER USER root@localhost IDENTIFIED BY 'MY_PW1234';

docker logs

  • Container쪽에서 생성된 stdout, stderr단의 로그를 읽어옵니다. ex) '2>&1'

  • --follow 옵션을 사용하여 로그가 계속적으로 스트리밍이 됩니다.

profile
데이터 엔지니어를 꿈꾸는 거북이, 한걸음 한걸음

0개의 댓글