0511_Docker_Flask, Django, C를 활용한 이미지 생성

HYOJU DO·2022년 5월 11일
2

Docker

목록 보기
1/3
post-thumbnail

Tip : 쉘 변수 지정


a.sh

#!/bin/sh
echo "\$MSG = $MSG"
echo "\$@ = $@"
echo "\$0 = $0"
echo "\$1 = $1"
echo "\$2 = $2"
echo "\$3 = $3"               
$ chmod +x ./a.sh

$ ./a.sh abc xyz 123 456 789
  • ./a.sh : 명령어
  • abc xyz 123 456 789 : argument
$MSG =
$@ = abc xyz 123 456 789
$0 = ./a.sh
$1 = abc
$2 = xyz
$3 = 123

변수 사용 | `export` 명령어로 변수 내보내기
$ export MSG="hello MESSAGE"

$ ./a.sh abc xyz 123 456 789

$MSG = hello MESSAGE
$@ = abc xyz 123 456 789
$0 = ./a.sh
$1 = abc
$2 = xyz
$3 = 123

env 명령어에 나와야지 변수 참조해서 사용 가능

env | grep MSG
MSG=hello MESSAGE

해당 명령어에서만 사용할 변수 지정

$ MSG="hello world" ./a.sh 1 2 3 4

$MSG = hello world
$@ = 1 2 3 4
$0 = ./a.sh
$1 = 1
$2 = 2
$3 = 3

명령어(./a.sh) 앞에 변수 제공

/usr/local/bin/httpd-foreground

$ docker run -it httpd bash
root@61a0a2fc8887:/usr/local/apache2# cd /usr/local/bin/
root@61a0a2fc8887:/usr/local/bin# cat httpd-foreground
#!/bin/sh
set -e

# Apache gets grumpy about PID files pre-existing
rm -f /usr/local/apache2/logs/httpd.pid

exec httpd -DFOREGROUND "$@"

root@61a0a2fc8887:/usr/local/bin# ./httpd-foreground -l

$@ 의미 : 추가 argument가 $@ 자리에 들어감
== httpd -DFOREGROUND -l 명령어 실행


Ubuntu Timezone 설정


자동화 걸림돌 : 패키지 설치 시 y,n 질문

기준 : 관리자 권한이 필요한 도구인가?


tzdata : /usr/share/zoneinfo/* 을 만들어주는 패키지

ubuntu apache 이미지 Dockerfile

FROM ubuntu:focal

# 추가 파트
#설치 명령어 앞에 변수 제공
RUN apt update; DEBIAN_FRONTEND=noninteractive apt install tzdata
RUN ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime  # Asia, Seoul 대소문자 구분

RUN apt -y install apache2
COPY index.html /var/www/html/index.html
CMD ["/usr/sbin/apache2ctl","-DFOREGROUND"]
EXPOSE 80/tcp
  • 유닉스 계열은 파일 디렉토리 대소문자 구분
  • 윈도우는 대소문자 구분 x

+) 이미지 빌드 경고 메세지

`WARNING: apt does not have a stable CLI interface. Use with caution in scripts.`

apt가 apt-get의 모든 기능을 가지고 있진 않기 때문에 메세지가 뜨는 것



Dockerfile 이미지 Caching


빌드 진행 -> 이미지 commit 하여 caching
동일 이미지 한 번 더 빌드 시, caching 된 데이터 사용하여 실행


이미지 히스토리 확인
-> 빌드 과정이 캐싱 되어있음

$ docker history myweb:ubuntu

IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
070100084e4b   32 minutes ago   /bin/sh -c #(nop)  EXPOSE 80/tcp                0B
9774e69e4f56   32 minutes ago   /bin/sh -c #(nop)  CMD ["/usr/sbin/apache2ct…   0B
05f2b679bf1a   32 minutes ago   /bin/sh -c #(nop) COPY file:f97eab0e2aae3322…   20B
2ce211493257   32 minutes ago   /bin/sh -c apt -y install apache2               111MB
1f5d8e5a5237   34 minutes ago   /bin/sh -c ln -sf /usr/share/zoneinfo/Asia/S…   30B
7bf98b51baff   34 minutes ago   /bin/sh -c apt update; DEBIAN_FRONTEND=nonin…   39.5MB
53df61775e88   11 days ago      /bin/sh -c #(nop)  CMD ["bash"]                 0B
<missing>      11 days ago      /bin/sh -c #(nop) ADD file:7009ad0ee0bbe5ed7…   72.8MB


데이터 및 내용이 바뀌었을 때, Docker는 파일의 해시값을 확인하여
바뀐 데이터의 빌드 이전 과정은 캐싱 된 데이터를 사용하여 빌드하고 이후 과정은 새로 빌드 실행

문제점

명령어 자체의 형태가 바뀌지 않으면 계속 캐싱된 이미지 사용

-> 패치를 위해 apt update를 매번 새로 해줘야하는데 캐싱된 이미지를 사용하기 때문에 목록 업데이트 불가능

패키지 버전 표기
ex. RUN apt install -y apache2=2.4.1 -> 버전이 없다고 오류 발생 -> 수정 가능
-> 버전 표기하지 않으면 아무 오류 없이 동작하기 때문에 원하는 버전으로 패치 불가능

어떻게 업데이트 및 변경사항을 매번 적용시켜 빌드할 것인가?

  1. ONBUILD RUN apt update
    (in Dockerfile)
    빌드를 할 때마다 실행할 instruction 지정

  2. docker build -t myweb:ubuntu . --no-cache
    (명령어 옵션) -> 더 많이 사용
    모든 명령어에 캐싱하지 않기 때문에 시간은 오래 걸림
    빌드 시간 적인 측면에서는 ONBUILD가 더 효율적


Python 이미지 리눅스 별 용량 차이

$ docker image ls | grep python
python            3.10.4-buster           892MB
python            3.10.4-bullseye         920MB
python            3.10.4-slim-buster      118MB
python            3.10.4-alpine           47.8MB

busybox : 아주 작은 사이즈의 리눅스

  • 커널과 필수 바이너리만 존재
  • yum과 같은 패키지 관리자 x
    -> 소스코드를 받아서 실행파일을 직접 빌드해서 설치
    (매우 사용하기 불편 ->요즘 거의 사용 x)

alpine linux

  • apk 라고 하는 패키지 관리자 별도로 존재
  • bash shell 포함 x (본 쉘만 존재)
  • GlibC대신 musl 라이브러리 사용
    완벽호환 x -> 가끔 오류 발생
# docker run -it alpine:3 bash # 불가능
docker run -it alpine:3 sh

패키지 업데이트
apk update

패키지 검색

apk search

패키지 설치

apk add

패키지 제거

apk del

Python Dockerfile


python 파일 실행 권한 부여
vim hello.py
sudo chmod +x hello.py

Dockerfile

FROM python:3.9-buster
WORKDIR /root
ADD hello.py .
CMD ["python3","hello.py"]


파이썬은 표준 출력이 필요 없기 때문에 run 할 때 아무 옵션 없이 사용

$ docker build -t pyhello:v1 .
$ docker run pyhello:v1 

이미지 빌드

docker build -f Dockerfile-slim -t pyhello:v1-slim .

-f 옵션 : 도커 파일 지정 가능
절대경로 가능 ) -f /home/vagrant//python/hello-world/Dockerfile-alpine


Flask


마이크로 아키텍쳐 를 지원하는 웹 프레임워크 : 가벼움
간단한 파이썬 코드로 웹사이트 호스팅

python, python3-pip 설치가 선행되어야 함

PIP

Python 패키지 관리자


pip 패키지 설치
sudo apt install python3-pip -y

pip3 로 습관화

  • 데비안 : pip == pip3
  • Redhat : pip -> python2 / pip3 -> python3

(이 컴퓨터에 설치 된) 파이썬 패키지 목록
pip3 list

가상환경 (Virtual Environment)

해당되는 앱 전용 가상 환경을 설치하고 해당 가상 환경에 패키지 설치하고 사용할 수 있음
-> isolation

1) 가상환경 설치

python3-venv : python3 가상환경 만들 수 있는 라이브러리

💡 별도의 디렉토리 만들기

현재 ~/pythontest/flask 디렉토리에서 진행

$ mkdir flask
$ cd flask

설치

sudo apt install python3-venv -y

설치 확인

$ ls -a
.  ..  venv

가상환경/프로젝트 생성

$ python3 -m venv venv
  • 앞 venv == 모듈
  • 뒤 venv == 디렉토리 이름 (변경 가능)

2) 가상환경 활성화

작업 디렉토리 (~/pythontest/flask)에서 활성화

$ . venv/bin/activate
$ . myproject/bin/activate

💡프롬프트 모양 확인 필수

(venv)  vagrant@docker  ~/pythontest/flask 


패키지 목록 확인

(venv)  vagrant@docker  ~/pythontest/flask  pip3 list
Package       Version
------------- -------
pip           20.0.2
pkg-resources 0.0.0
setuptools    44.0.0
(venv)  vagrant@docker  ~/pythontest/flask 


가상환경 비활성화

(venv)  vagrant@docker  ~/pythontest/flask  deactivate

💡 컨테이너에서도 가상 환경을 만드는 이유가 있을까?

패키지를 설치할 때 가상환경 설치

글로벌 패키지와 내가 사용할 패키지 간 충돌이 발생할 수 있기 때문에
안전하게 이미지를 말기 위해서 격리 된 가상환경 사용



Flask 가상환경 사용하여 'Hello' 어플리케이션 실행


Flask 설치

가상 환경에서 설치

(venv)  vagrant@docker  ~/pythontest/flask  pip3 install Flask

hello.py

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hello, World!</p>"

@app.route("/") : 데코레이터 -> URL 경로(여기서는 root)로 오면 해당 함수를 실행하라

$ export FLASK_APP=hello
$ flask run
* Running on http://127.0.0.1:5000/*
 
==

python3 -m flask run

새로 터미널 열어서 확인

$ curl localhost:5000
<p>Hello, World!</p>%

해당 함수 내용 출력

@app.route("/hello") 로 변경 or 추가 후 flask run

$ curl localhost:5000/hello

변경 시, /hello를 추가해야 정상적으로 실행
추가 시, localhost:5000와 localhost:5000/hello 둘 다 가능


외부 접근

원래 외부에서는 접근 불가 -> http://127.0.0.1:5000 -> 로컬(127.0.0.1)에서만 접근 가능

--host 옵션으로 외부 접근 허용

flask run --host=0.0.0.0


포트 변경

flask run --host=0.0.0.0 --port=8080

Flask 기본 포트 : 5000


탬플릿 렌더링

hello.py

from flask import Flask
from flask import render_template

app = Flask(__name__)

@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
    return render_template('hello.html', name=name)

templates/hello.html

<!doctype html>
<title>Hello from Flask</title>
{% if name %}                     # name 값 존재
  <h1>Hello :) {{ name }}!</h1>
{% else %}                        # name 값 존재 x
  <h1>Hello, World!</h1>
{% endif %}


💡pip3 freezing


가상환경에서 freezing
(venv)  vagrant@docker  ~/pythontest/flask  pip3 freeze > requirements.txt

requirements.txt

click==8.1.3
Flask==2.1.2
importlib-metadata==4.11.3
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.1
Werkzeug==2.1.2
zipp==3.8.0

freeze 한 값을 requirements.txt 파일에 저장 (관습)
-> 현재 설치 된 패키지 목록이 저장


Python 패키지들은 보통 아래와 같은 구성으로 제공

$ ls
hello.py  requirements.txt  templates

Requirements.txt로 Python 패키지 다운

vagrant@docker  ~/pythontest/flask  pip3 install -r requirements.txt

-r : 해당 파일을 읽어서 패키지를 설치



Django


풀스택 웹 프레임워크 (프론트앤드, 백앤드 + 백오피스) : 무거움

Django 튜토리얼

Flask와 동일하게 가상 환경 구성하고 활성화

mkdir ~/pythontest/Django
cd ~/pythontest/Django
python3 -m venv djangoapp
. djangoapp/bin/activate

Django 설치

가상 환경에서 설치

pip3 install Django
pip3 freeze > requirements.txt

프로젝트 생성

프로젝트 : Django app의 껍데기

django-admin startproject mysite

mysite : 프로젝트명

작업 디렉토리 : ~/pythontest/Django

$ ls
djangoapp  mysite  requirements.txt   # djangoapp : 가상환경

Django는 mysiterequirements.txt 를 기본 제공해야함


실행

프레임워크 vs 라이브러리

  • 프레임워크 : 프레임(틀)이 있고 프레임 워크가 시키는 대로 구성을 짜고 그 안에 필요한 데이터를 넣음
  • 라이브러리 : 우리 코드가 잇고 중간중간 코드에 해당하는 라이브러리 사용


실행

cd mysite
python3 manage.py runserver

Django 디폴트 : http://127.0.0.1:8000/ -> 외부 접속 불가


새로 터미널 열어서 확인

curl localhost:8000

외부 접속

python3 manage.py runserver 0.0.0.0:8000  # 주소:포트번호

접속 불가


`mysite 앱`으로 이동
cd mysite

~/pythontest/Django/mysite/mysite/settings.py

...
ALLOWED_HOSTS = []        # local에서만 접속
ALLOWED_HOSTS = ['*']     # 외부 접속
...

~/pythontest/Django/mysite 로 나와서 실행



Django Polls app 생성


polls 디렉토리(app) 생성

가상환경에서 생성

(djangoapp)  vagrant@docker  ~/pythontest/Django/mysite  python manage.py startapp polls
$ ls
db.sqlite3  manage.py  mysite  polls 

polls/views.py

from django.shortcuts import render

from django.http import HttpResponse


def index(request):
    return HttpResponse("Hello, world. You're at the polls index."

index 함수로 요청하면 http로 응답

라우팅

~/pythontest/Django/mysite/polls/urls.py : app의 url

from django.urls import path

from . import views

urlpatterns = [
    path('', views.index, name='index'),    # '' : root
]

root로 들어오면 index 함수 실행

💡 작업 위치 주의

~/pythontest/Django/mysite/mysite/urls.py : project의 url

from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('polls/', include('polls.urls')),
    path('admin/', admin.site.urls),
]

실행

python manage.py runserver 0.0.0.0:8000



C 명령어 실행 이미지


Language

  • 컴파일 : C, C++, Golang, Rust -> cpu가 바로 실행시킬 실행파일 만들어줘야함 (빠름)
  • Java, .NET(C#) -> Bytecode 생성
  • 스크립트 : Shell, Perl, Python, Ruby, Javascript
    - 반드시 인터프리터 / 런타임 이라고 하는 해석기 필요

이미지 생성

디렉토리 : /home/vagrant

mkdir clang
cd clang

hello.c

#include <stdio.h>

int main() {
        printf("Hello C World\n");
        return 0;
}

해당 스크립트 바로 실행 불가
항상 실행파일으로 만들어줘야함 -> 컴파일

GCC 컴파일러

리눅스에서는 gcc 라는 컴파일러 사용

sudo apt install gcc

컴파일

gcc hello.c -o hello

실행 파일 확인

$ file hello

실행

$ ./hello

이미지 말기

Dockerfile

FROM ubuntu:focal
ADD hello /root
CMD ["/root/hello"]
$ docker build -t chello .
$ docker run chello

문제점 : 이미지 크기 너무 큼(72.8MB) -> 효율성 x



효율적인 C 이미지 생성


1. scratch 이미지

아무 것도 없는 이미지
스크립트 언어는 사용 불가


FROM이 없으면 빌드가 안됨 -> base 이미지 자리에 scratch 대신 넣음

Dokerfile

FROM scratch

ADD hello /root
CMD ["/root/hello"]

1) 동적 바이너리

동적으로 라이브러리 참조

$ file hello
hello: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=2116073347e3a13dedb56271596ee4e6fb4050b0, for GNU/Linux 3.2.0, not stripped
  • ELF : 실행파일
  • dynamically linked
    -> 관련 라이브러리 필요
    interpreter ... (language의 인터프리터와 다름)
    so 라고 붙어있는 것은 전부 C의 라이브러리

ldd : 필요한 라이브러리 확인

$ ldd hello
        linux-vdso.so.1 (0x00007fff7c4f3000)   # 실제 없는 파일(메모리 주소에 올라가 있음)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f32447bd000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f32449bd000)

해당 라이브러리들을 포함시켜줘야 참조 후 실행 가능

빈 디렉토리 생성 후 ldd hello 에서 확인했던 라이브러리 복사 후 컨테이너 내부로 제공

mkdir -p lib/x86_64-linux-gnu/
mkdir -p lib64
cp /lib/x86_64-linux-gnu/libc.so.6 lib/x86_64-linux-gnu
cp /lib64/ld-linux-x86-64.so.2 lib64/

libc : C언어의 핵심 라이브러리

Dockerfile

FROM scratch
ADD hello /
ADD lib /lib
ADD lib64/ /lib64
CMD ["/hello"]

이미지 말고 실행
docker build -t chello .
docker run chello

hello 실행파일은 참조하는 라이브러리가 2개가 있기 때문에
디렉토리 경로 같은 3가지 파일 (hello + 라이브러리 2개)을 묶어서 이미지 생성


2) 정적 바이너리

라이브러리까지 실행파일 내에 다 넣은 것

gcc hello.c -o hellos -static
$ file hellos
hellos: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=892d801da71316deb3bcef57c98a278cf1fefa28, for GNU/Linux 3.2.0, not stripped

statically linked

Dockerfile

FROM scratch
ADD hello /
CMD ["/hellos"]
docker build -t chellos .
docker run chellos

동적 바이너리 vs 정적 바이너리 사이즈 비교

$ ls -lh hello hellos
-rwxrwxr-x 1 vagrant vagrant  17K May 11 22:34 hello
-rwxrwxr-x 1 vagrant vagrant 852K May 11 23:15 hellos



2. multi-stage 빌드

컴파일 언어에서 중요

예시 Dockerfile

FROM golang:1.16-alpine AS build
RUN apk add --no-cache git
RUN go get github.com/golang/dep/cmd/dep
COPY Gopkg.lock Gopkg.toml /go/src/project/
WORKDIR /go/src/project/
RUN dep ensure -vendor-only
COPY . /go/src/project/
RUN go build -o /bin/project    # 실행파일 만들어 줌 (이미지로 만들진 x)

FROM scratch
COPY --from=build /bin/project /bin/project
ENTRYPOINT ["/bin/project"]
CMD ["--help"]

기준 : FROM -> 2번 빌드
처음 스테이지에서 만든 파일을 다음 스테이지로 넘김

  • AS build : 해당 과정을 build라고 취급하겠다 (반드시 필요)
  • COPY --from=build : 위 스테이지의 build 참조하여 스크래치 파일로 가져옴
    COPY --from=build /bin/project(위 스테이지 파일) /bin/project(해당 스테이지 파일)
    (build는 이름 변경 가능 -> AS xyz , COPY --from=xyz)
    ADD는 안되고 COPY에서만 가능

GCC 이미지 사용

Dockerfile

# gcc 컨테이너
FROM gcc AS cbuilder
WORKDIR /root
ADD hello.c .
RUN gcc hello.c -static -o hello

# scratch 컨테이너
FROM scratch
COPY --from=cbuilder /root/hello /   # ADD 불가능
CMD ["/hello"]
$ docker build -t chellos .
$ docker run chellos

레이어 확인
$ docker save chellos -o hello.tar
$ mkdir source   # 디렉토리 만들고 압축 해제
$ tar xf hello.tar -C source
$ cd source
$ cd d4887b54f48d67881363c7da18c28af3f77bcef809a002112e1c991faf6530dd   # 레이어 1개
$ ls
VERSION  json  layer.tar
$ tar xf layer.tar
$ ls
VERSION  hello  json  layer.tar   # hello 파일 1개 존재
$ ./hello  # 명령어 실행
Hello C World

컨테이너 2개(gcc,scratch) 생성 한 후 2번째 컨테이너(scratch)로 최종 이미지 commit

profile
Be on CLOUD nine! ☁️ ( 수정 예정 == 📌)

0개의 댓글