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
$MSG =
$@ = abc xyz 123 456 789
$0 = ./a.sh
$1 = abc
$2 = xyz
$3 = 123
$ 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 명령어 실행
자동화 걸림돌 : 패키지 설치 시 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
유닉스
계열은 파일 디렉토리 대소문자 구분
+) 이미지 빌드 경고 메세지
`WARNING: apt does not have a stable CLI interface. Use with caution in scripts.`
apt가 apt-get의 모든 기능을 가지고 있진 않기 때문에 메세지가 뜨는 것
빌드 진행 -> 이미지 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 -> 버전이 없다고 오류 발생 -> 수정 가능
-> 버전 표기하지 않으면 아무 오류 없이 동작하기 때문에 원하는 버전으로 패치 불가능
어떻게 업데이트 및 변경사항을 매번 적용시켜 빌드할 것인가?
ONBUILD RUN apt update
(in Dockerfile)
빌드를 할 때마다 실행할 instruction 지정
- 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
: 아주 작은 사이즈의 리눅스
alpine linux
apk
라고 하는 패키지 관리자 별도로 존재bash shell 포함 x
(본 쉘만 존재)musl
라이브러리 사용# docker run -it alpine:3 bash # 불가능
docker run -it alpine:3 sh
apk update
패키지 검색
apk search
패키지 설치
apk add
패키지 제거
apk del
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
마이크로 아키텍쳐
를 지원하는 웹 프레임워크 : 가벼움
간단한 파이썬 코드로 웹사이트 호스팅
python, python3-pip 설치가 선행되어야 함
Python 패키지 관리자
sudo apt install python3-pip -y
pip3
로 습관화
pip3 list
해당되는 앱 전용 가상 환경을 설치하고 해당 가상 환경에 패키지 설치하고 사용할 수 있음
-> isolation
python3-venv : python3 가상환경 만들 수 있는 라이브러리
💡 별도의 디렉토리 만들기
현재 ~/pythontest/flask
디렉토리에서 진행
$ mkdir flask
$ cd flask
설치
sudo apt install python3-venv -y
설치 확인
$ ls -a
. .. venv
가상환경/프로젝트 생성
$ python3 -m venv venv
작업 디렉토리 (~/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
💡 컨테이너에서도 가상 환경을 만드는 이유가 있을까?
패키지를 설치할 때 가상환경 설치
글로벌 패키지와 내가 사용할 패키지 간 충돌이 발생할 수 있기 때문에
안전하게 이미지를 말기 위해서 격리 된 가상환경 사용
가상 환경에서 설치
(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 %}
(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
vagrant@docker ~/pythontest/flask pip3 install -r requirements.txt
-r : 해당 파일을 읽어서 패키지를 설치
풀스택 웹 프레임워크 (프론트앤드, 백앤드 + 백오피스) : 무거움
Flask와 동일하게 가상 환경 구성하고 활성화
mkdir ~/pythontest/Django
cd ~/pythontest/Django
python3 -m venv djangoapp
. djangoapp/bin/activate
가상 환경에서 설치
pip3 install Django
pip3 freeze > requirements.txt
프로젝트
: Django app의 껍데기
django-admin startproject mysite
mysite : 프로젝트명
작업 디렉토리 : ~/pythontest/Django
$ ls
djangoapp mysite requirements.txt # djangoapp : 가상환경
Django는
mysite
와requirements.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 # 주소:포트번호
접속 불가
cd mysite
~/pythontest/Django/mysite/mysite/settings.py
...
ALLOWED_HOSTS = [] # local에서만 접속
ALLOWED_HOSTS = ['*'] # 외부 접속
...
~/pythontest/Django/mysite
로 나와서 실행
가상환경에서 생성
(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
Language
디렉토리 : /home/vagrant
mkdir clang
cd clang
hello.c
#include <stdio.h>
int main() {
printf("Hello C World\n");
return 0;
}
해당 스크립트 바로 실행 불가
항상 실행파일으로 만들어줘야함 -> 컴파일
리눅스에서는 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
아무 것도 없는 이미지
스크립트 언어는 사용 불가
FROM이 없으면 빌드가 안됨 -> base 이미지 자리에 scratch 대신 넣음
Dokerfile
FROM scratch
ADD hello /root
CMD ["/root/hello"]
동적으로 라이브러리 참조
$ 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
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개)을 묶어서 이미지 생성
라이브러리까지 실행파일 내에 다 넣은 것
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
컴파일 언어에서 중요
예시 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에서만 가능
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