다음 중 하나 이상 경험한 사람
다른 컴퓨터에서 빌드 에러가 나는 경우
빌드툴이 발전하면서 많이 나아졌지만 그래도 다른 OS문제라던지 여전히 가끔씩 접하는 케이스이다.
해결법
Dockerfile 을 사용한다
DB를 연결해야 되는데 내 컴퓨터를 사용하고 싶지 않은 경우
- 웹개발을 할때 프로덕션과 동일한 환경으로 세팅해야 하지만 귀찮다
- 이미 내 데이터베이스는 사용중이거나 혹은 귀찮다
- 크롤링을 하면서 데이터베이스에 저장하고 싶은데 데이터베이스 설정하기가 귀찮다
해결법
docker-compose로 해결한다.
배포 너무 귀찮다..
배포 환경에서 환경설정 등 여러가지로 설정을 잡아 주기 귀찮은 상황도 docker로 해결 할 수 있다.
해결법
docker stack deploy 를 사용한다
서비스 scalability도 쉽게 했으면 좋겠다..
해결법
docker service scale frontend=10 한줄이면 된다.
Docker로 개발하면 아래와 같은 프로세스로 개발을 하게 된다
Dockerfile작성docker-compose.yml작성- 개발
docker-compose up
- 배포
- 이미지화 및
registry docker stack deploy
- 이미지화 및
http://labs.play-with-docker.com/
Dockerfile은 docker image를 작성하는 간단한 스크립트이다Dockerfile로 image가 만들어지면 container로 실행이 가능하게 된다.- Virtualbox로 치면
- image = iso파일
- container = 실제 실행되는 가상환경
- 물론 docker는 virtualization이 아니라 호스트의 커널을 사용해서 사실상 Native Application이다.
예시로 아래와 같은 Flask App이 있다.
from flask import Flask, render_template
import random
app = Flask(__name__)
# list of cat images
images = [
"http://ak-hdl.buzzfed.com/static/2013-10/enhanced/webdr05/15/9/anigif_enhanced-buzz-26388-1381844103-11.gif",
"http://ak-hdl.buzzfed.com/static/2013-10/enhanced/webdr01/15/9/anigif_enhanced-buzz-31540-1381844535-8.gif",
"http://ak-hdl.buzzfed.com/static/2013-10/enhanced/webdr05/15/9/anigif_enhanced-buzz-26390-1381844163-18.gif",
"http://ak-hdl.buzzfed.com/static/2013-10/enhanced/webdr06/15/10/anigif_enhanced-buzz-1376-1381846217-0.gif",
"http://ak-hdl.buzzfed.com/static/2013-10/enhanced/webdr03/15/9/anigif_enhanced-buzz-3391-1381844336-26.gif",
"http://ak-hdl.buzzfed.com/static/2013-10/enhanced/webdr06/15/10/anigif_enhanced-buzz-29111-1381845968-0.gif",
"http://ak-hdl.buzzfed.com/static/2013-10/enhanced/webdr03/15/9/anigif_enhanced-buzz-3409-1381844582-13.gif",
"http://ak-hdl.buzzfed.com/static/2013-10/enhanced/webdr02/15/9/anigif_enhanced-buzz-19667-1381844937-10.gif",
"http://ak-hdl.buzzfed.com/static/2013-10/enhanced/webdr05/15/9/anigif_enhanced-buzz-26358-1381845043-13.gif",
"http://ak-hdl.buzzfed.com/static/2013-10/enhanced/webdr06/15/9/anigif_enhanced-buzz-18774-1381844645-6.gif",
"http://ak-hdl.buzzfed.com/static/2013-10/enhanced/webdr06/15/9/anigif_enhanced-buzz-25158-1381844793-0.gif",
"http://ak-hdl.buzzfed.com/static/2013-10/enhanced/webdr03/15/10/anigif_enhanced-buzz-11980-1381846269-1.gif"
]
@app.route('/')
def index():
url = random.choice(images)
return render_template('index.html', url=url)
if __name__ == "__main__":
app.run(host="0.0.0.0")프로젝트 구조는 다음과 같다.
.
├── app.py
├── requirements.txt
└── templates
└── index.html
위 프로젝트로 Dockerfile 을 작성하면 다음과 같다.
# FROM: 베이스 이미지를 정해준다.
FROM python:3-alpine
# requirements.txt 파일 복사
COPY requirements.txt /code/
RUN pip install --no-cache-dir -r /code/requirements.txt
# app.py와 index.html 복사
COPY app.py /code/
COPY templates/index.html /code/templates/
EXPOSE 5000
WORKDIR /code
CMD ["python", "app.py"]외울 필요는 없고 필요할때마다 Dockerfile Reference 에서 찾아보면 된다
몇가지 포인트는
FROM- 베이스 이미지를 정해준다. 새로 만드는 것보다 official image 위를 이용하는게 좋고 alpine은 리눅스 종류 중 하나인데 용량이 5mb에 불과해 대부분 모든 공식 이미지들이 alpine을 지원한다.
EXPOSE- 포트 5000을 열겠다는 소리이지만 실제로 아무 일도 하지 않는다. 사용자에게 포트 5000번에서 이 어플리케이션이 돌아간다고 알려주는 용도이다. 그렇지 않으면 포트 몇번을 연결해줘야 되는지 헷갈리기 때문이다. 실제로는
ENV혹은ARG로 설정해준다.
이제 이걸 이미지로 빌드한다
docker build -t my_flask_app .Sending build context to Docker daemon 7.68kB Step 1/8 : FROM python:3-alpine ---> d26cf7d4701d Step 2/8 : COPY requirements.txt /code/ ---> Using cache ---> 459e0df94893 Step 3/8 : RUN pip install --no-cache-dir -r /code/requirements.txt ---> Using cache ---> 5a618308e9b8 Step 4/8 : COPY app.py /code/ ---> Using cache ---> e35197a2ee91 Step 5/8 : COPY templates/index.html /code/templates/ ---> Using cache ---> 734c689d69e3 Step 6/8 : EXPOSE 5000 ---> Using cache ---> 0c4d7984da84 Step 7/8 : WORKDIR /code ---> Using cache ---> 89bfc8e45446 Step 8/8 : CMD python app.py ---> Using cache ---> 51fb98413e28 Successfully built 51fb98413e28 Successfully tagged my_flask_app:latest
빌드가 되었는지 확인을 한다
docker image lsREPOSITORY TAG IMAGE ID CREATED SIZE my_flask_app latest 51fb98413e28 20 seconds ago 98.7MB
image를 container로 실행은 다음과 같다.
docker container run --name flask --publish 8080:5000 -d my_flask_appfee755b1f3b34bff0b8d8f676a696976cfe4b24b9555b7257428c56ab799424d
구) 커맨드인 docker run 으로 해도 되지만 신명령어는 docker <<context>> <<command>> 형태를 사용한다.
즉 여기서는 docker container run 인 것이다. 커맨드도 매번 --help 를 보면 된다.
docker container --helpUsage: docker container COMMAND
Manage containers
Options:
--help Print usage
Commands:
attach Attach local standard input, output, and error streams to a running container
commit Create a new image from a container's changes
cp Copy files/folders between a container and the local filesystem
create Create a new container
diff Inspect changes to files or directories on a container's filesystem
exec Run a command in a running container
export Export a container's filesystem as a tar archive
inspect Display detailed information on one or more containers
kill Kill one or more running containers
logs Fetch the logs of a container
ls List containers
pause Pause all processes within one or more containers
port List port mappings or a specific mapping for the container
prune Remove all stopped containers
rename Rename a container
restart Restart one or more containers
rm Remove one or more containers
run Run a command in a new container
start Start one or more stopped containers
stats Display a live stream of container(s) resource usage statistics
stop Stop one or more running containers
top Display the running processes of a container
unpause Unpause all processes within one or more containers
update Update configuration of one or more containers
wait Block until one or more containers stop, then print their exit codes
Run 'docker container COMMAND --help' for more information on a command.
어쨌든 docker container run 이후 container id가 반환되었으면 성공적으로 실행된 것이다.
확인을 하면 동작중인것을 알 수 있다.
GET 127.0.0.1:8080HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 732
Server: Werkzeug/0.12.2 Python/3.6.3
Date: Sat, 07 Oct 2017 08:19:33 GMT
<html>
<head>
<style type="text/css">
body {
background: black;
color: white;
}
div.container {
max-width: 500px;
margin: 100px auto;
border: 20px solid white;
padding: 10px;
text-align: center;
}
h4 {
text-transform: uppercase;
}
</style>
</head>
<body>
<div class="container">
<h4>Cat Gif of the day</h4>
<img src="http://ak-hdl.buzzfed.com/static/2013-10/enhanced/webdr05/15/9/anigif_enhanced-buzz-26358-1381845043-13.gif" />
<p><small>Courtesy: <a href="http://www.buzzfeed.com/copyranter/the-best-cat-gif-post-in-the-history-of-cat-gifs">Buzzfeed</a></small></p>
</div>
</body>
</html>
아래와 같이 실행중인 컨테이너를 확인할 수 있다.
docker container ls -aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES fee755b1f3b3 my_flask_app "python app.py" 27 minutes ago Up 27 minutes 0.0.0.0:8080->5000/tcp flask
귀찮으니 force로 지운다
docker container rm -f flask없어졌다.
docker container ls -aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
실제로 docker를 사용할때는 프로젝트를 시작하기전에 Dockerfile 과 docker-compose.yml 를 미리 만들어놓고 시작을 한다.
이제부터는 허접한 DevOps 라고 가정하고 다음과 같은 일을 해야 한다고 가정한다.
- drupal 8.2 버전을 설치한다
- drupal bootstrap theme을 설치한다
- port는 8080을 이용한다.
- database는 postgres 9.6 버전을 사용한다
- postgres password는 qwer1234qwer 로 한다
- 데이터는 container가 날아가도 보존하도록 한다
우선 가장 먼저 할일은 Dockerfile 을 만드는 일이다. 이 경우는 없어도 되긴 하지만 그냥 한다.
FROM drupal:8.2
RUN apt-get update \
&& apt-get install -y git \
&& rm -rf /var/lib/apt/lists*
WORKDIR /var/www/html/themes
RUN git clone --branch 8.x-3.x --single-branch --depth 1 https://git.drupal.org/project/bootstrap.git \
&& chown -R www-data:www-data bootstrap
WORKDIR /var/www/html/var/www/html 이나 이런건 어떻게 알수 있는가? Dockerhub/drupal 에서 올려놓은 README 를 보면 된다.
docker-compose.yml 은 다음과 같다.
version: "3"
services:
drupal:
build: .
image: custom-drupal
depends_on:
- db
ports:
- 8080:80
volumes:
- drupal-modules:/var/www/html/modules
- drupal-profiles:/var/www/html/profiles
- drupal-sites:/var/www/html/sites
- drupal-themes:/var/www/html/themes
db:
image: postgres:9.6
environment:
POSTGRES_PASSWORD: qwer1234qwer
volumes:
- drupal-data:/var/lib/postgresql/data
volumes:
drupal-data:
drupal-modules:
drupal-profiles:
drupal-sites:
drupal-themes:yaml포맷을 사용한다.- volumes은 실제 호스트에 볼륨을 사용해 콘테이너가 없어지더라도 데이터가 남아있게 된다.
- 아무것도 작성하지 않으면
docker volume을 사용한다.
- 아무것도 작성하지 않으면
environment를 통해 환경변수를 설정할 수 있다.- 이런 secret들은 환경변수로 설정하는건 좋지 않다.
- 실제로는
docker secret을 사용한다.
자세한 키워드는 Docker Compose Reference 에서 확인한다.
이제 실행은 docker-compose up 을 사용한다.
먼저 help를 살펴본다.
docker-compose --helpDefine and run multi-container applications with Docker.
Usage:
docker-compose [-f <arg>...] [options] [COMMAND] [ARGS...]
docker-compose -h|--help
Options:
-f, --file FILE Specify an alternate compose file (default: docker-compose.yml)
-p, --project-name NAME Specify an alternate project name (default: directory name)
--verbose Show more output
--no-ansi Do not print ANSI control characters
-v, --version Print version and exit
-H, --host HOST Daemon socket to connect to
--tls Use TLS; implied by --tlsverify
--tlscacert CA_PATH Trust certs signed only by this CA
--tlscert CLIENT_CERT_PATH Path to TLS certificate file
--tlskey TLS_KEY_PATH Path to TLS key file
--tlsverify Use TLS and verify the remote
--skip-hostname-check Don't check the daemon's hostname against the name specified
in the client certificate (for example if your docker host
is an IP address)
--project-directory PATH Specify an alternate working directory
(default: the path of the Compose file)
Commands:
build Build or rebuild services
bundle Generate a Docker bundle from the Compose file
config Validate and view the Compose file
create Create services
down Stop and remove containers, networks, images, and volumes
events Receive real time events from containers
exec Execute a command in a running container
help Get help on a command
images List images
kill Kill containers
logs View output from containers
pause Pause services
port Print the public port for a port binding
ps List containers
pull Pull service images
push Push service images
restart Restart services
rm Remove stopped containers
run Run a one-off command
scale Set number of containers for a service
start Start services
stop Stop services
top Display the running processes
unpause Unpause services
up Create and start containers
version Show the Docker-Compose version information
이제 실행을 한다.
docker-compose up -ddocker container ls -aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4d448b78b4fd custom-drupal "docker-php-entryp..." 19 seconds ago Up 18 seconds 0.0.0.0:8080->80/tcp drupal_drupal_1 4f2cf2a1722f postgres:9.6 "docker-entrypoint..." 19 seconds ago Up 18 seconds 5432/tcp drupal_db_1
이제 로컬호스트:8080으로 가보면
GET 127.0.0.1:8080HTTP/1.1 302 Found
Date: Sat, 07 Oct 2017 09:04:22 GMT
Server: Apache/2.4.10 (Debian)
X-Powered-By: PHP/7.1.5
Cache-Control: no-cache
Location: /core/install.php
Content-Length: 312
Content-Type: text/html; charset=UTF-8
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="refresh" content="1;url=/core/install.php" />
<title>Redirecting to /core/install.php</title>
</head>
<body>
Redirecting to <a href="/core/install.php">/core/install.php</a>.
</body>
</html>
drupal 설치로 연결되는 것을 볼 수 있다.
중요한 점은 db를 선택하는 시점에
- postgreSQL 선택
- DATABASE HOST는
docker-compose.yml에서 지정해준 이름인http://db이다 - USERNAME은 건드리지 않았기 때문에 postgres 기본값인
postgres이다 - PASSWORD는
qwer1234qwer이다
실전으로 다음과 같은 architecture를 배포해야 되는 일인 경우,
docker-compose.yml 은 아래와 같다.
version: "3"
services:
vote:
image: dockersamples/examplevotingapp_vote:before
depends_on:
- redis
deploy:
replicas: 2
update_config:
parallelism: 2
restart_policy:
condition: on-failure
ports:
- 80:80
networks:
- frontend
redis:
image: redis:3.2
deploy:
replicas: 2
update_config:
parallelism: 2
delay: 10s
restart_policy:
condition: on-failure
networks:
- frontend
worker:
image: dockersamples/examplevotingapp_worker
deploy:
placement:
constraints: [node.role == manager]
replicas: 1
labels: [APP=VOTING]
restart_policy:
condition: on-failure
delay: 10s
max_attempts: 3
window: 120s
networks:
- frontend
- backend
db:
image: postgres:9.4
volumes:
- db-data:/var/lib/postgresql/data
deploy:
replicas: 1
placement:
constraints: [node.role == manager]
networks:
- backend
result:
image: dockersamples/examplevotingapp_result:before
ports:
- 5001:80
networks:
- backend
deploy:
replicas: 1
update_config:
parallelism: 2
delay: 10s
restart_policy:
condition: on-failure
depends_on:
- db
visualizer:
image: dockersamples/visualizer:stable
ports:
- 8080:8080
stop_grace_period: 1m30s
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
deploy:
placement:
constraints: [node.role == manager]
networks:
frontend:
backend:
volumes:
db-data:networks:
frontend:
backend:이 부분은 docker network 로 네트워크가 저절로 생성 된후 각각의 콘테이너가 속하는 오버레이에서만 서로 통신이 가능하다.
docker network create --driver overlay frontend
docker network create --driver overlay backend실제 배포를 할 때는 docker swarm 을 이용해 여러대의 노드로 클러스터를 구성한다음
docker stack deploy -c docker-compose.yml voteapp이전 docker-compose.yml 에서 deploy 에 들어간 옵션들이 docker stack deploy 를 할때 적용되는 옵션들인 것이다.
결론은
docker-compose는 development 할 때 사용하는 명령어docker stack deploy는 실제 배포할 때 사용 되는 명령어docker stack deploy를 할 경우 Dockerfile로 빌드를 못한다- 배포 이전에 내 이미지를 만들어
registry로 모든 노드에 접속할 수 있게 해준다.
- 배포 이전에 내 이미지를 만들어
registry는 로컬 전용 dockerhub 라고 생각하면 된다. 스웜 클러스터에 자동으로 공유가 된다.
다음과 같이 사용한다.
docker service create --name registry -p 5000:5000 registry
docker tag my_app 127.0.0.1:5000/my_app
docker push 127.0.0.1:5000/my_app이제 모든 노드에서 다음과 같이 내 이미지를 다운 받을 수 있다.
docker service create --name my_app 127.0.0.1:5000/my_appdocker secret create psql_user psql_user.txt혹은
echo "postgresuser" | docker secret create psql_user -로 생성한다. (끝에 - 가 중요하다)
실제 컨테이너에서는 /run/secrets/psql_user 로 접근이 가능하다. 파일처럼 보이지만 파일이 아니라 메모리에 저장되어있다.
services:
psql:
image: postgres
environment:
POSTGRES_USER_FILE: /run/secrets/psql_user
POSTGRES_PASSWORD_FILE: /run/secrets/psql_pass
secrets:
- psql_user
- psql_pass
secrets:
psql_user:
file: ./psql_user.txt
psql_pass:
file: ./psql_pass.txt으로 사용한다. 하지만 보안 이슈로 파일로 저장하기보다 external: true 세팅을 한 후 docker secret create 으로 생성한다.
secrets:
psql_user:
external: true
psql_pass:
external: true다음과 같은 파일로 관리를 한다.
- docker-compose.yml
- 이미지만 설정되어있는 베이스 파일
- docker-compose.override.yml
- 개발시 사용되는 config
- docker-compose.prod.yml
- 실제 배포시 사용되는 config
- docker-compose.test.yml
- CI에서 사용되는 config
docker-compose.yml
version: '3.1'
services:
drupal:
image: drupal:latest
postgres:
image: postgres:9.6docker-compose.override.yml
version: '3.1'
services:
drupal:
build: .
ports:
- "8080:80"
volumes:
- drupal-modules:/var/www/html/modules
- drupal-profiles:/var/www/html/profiles
- drupal-sites:/var/www/html/sites
- ./themes:/var/www/html/themes
postgres:
environment:
- POSTGRES_PASSWORD_FILE=/run/secrets/psql-pw
secrets:
- psql-pw
volumes:
- drupal-data:/var/lib/postgresql/data
volumes:
drupal-data:
drupal-modules:
drupal-profiles:
drupal-sites:
drupal-themes:
secrets:
psql-pw:
file: psql-fake-password.txtdocker-compose.prod.yml
version: '3.1'
services:
drupal:
ports:
- "80:80"
volumes:
- drupal-modules:/var/www/html/modules
- drupal-profiles:/var/www/html/profiles
- drupal-sites:/var/www/html/sites
- drupal-themes:/var/www/html/themes
postgres:
environment:
- POSTGRES_PASSWORD_FILE=/run/secrets/psql-pw
secrets:
- psql-pw
volumes:
- drupal-data:/var/lib/postgresql/data
volumes:
drupal-data:
drupal-modules:
drupal-profiles:
drupal-sites:
drupal-themes:
secrets:
psql-pw:
external: truedocker-compose.test.yml
version: '3.1'
services:
drupal:
image: drupal
build: .
ports:
- "80:80"
postgres:
environment:
- POSTGRES_PASSWORD_FILE=/run/secrets/psql-pw
secrets:
- psql-pw
volumes:
- ./sample-data:/var/lib/postgresql/data
secrets:
psql-pw:
file: psql-fake-password.txt# if you're doing anything beyond your local machine, please pin this to a specific version at https://hub.docker.com/_/node/
FROM node:6
RUN mkdir -p /opt/app
# set our node environment, either development or production
# defaults to production, compose overrides this to development on build and run
ARG NODE_ENV=production
ENV NODE_ENV $NODE_ENV
# default to port 80 for node, and 5858 or 9229 for debug
ARG PORT=80
ENV PORT $PORT
EXPOSE $PORT 5858 9229
# check every 30s to ensure this service returns HTTP 200
HEALTHCHECK CMD curl -fs http://localhost:$PORT/healthz || exit 1
# install dependencies first, in a different location for easier app bind mounting for local development
WORKDIR /opt
COPY package.json /opt
RUN npm install && npm cache clean --force
ENV PATH /opt/node_modules/.bin:$PATH
# copy in our source code last, as it changes the most
WORKDIR /opt/app
COPY . /opt/app
# if you want to use npm start instead, then use `docker run --init in production`
# so that signals are passed properly. Note the code in index.js is needed to catch Docker signals
# using node here is still more graceful stopping then npm with --init afaik
# I still can't come up with a good production way to run with npm and graceful shutdown
CMD [ "node", "index.js" ]FROM ubuntu:latest
# Elixir requires UTF-8
RUN apt-get update && apt-get upgrade -y && apt-get install locales && locale-gen en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
# update and install software
RUN apt-get install -y curl wget git make sudo \
# download and install Erlang apt repo package
&& wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb \
&& dpkg -i erlang-solutions_1.0_all.deb \
&& apt-get update \
&& rm erlang-solutions_1.0_all.deb \
# For some reason, installing Elixir tries to remove this file
# and if it doesn't exist, Elixir won't install. So, we create it.
# Thanks Daniel Berkompas for this tip.
# http://blog.danielberkompas.com
&& touch /etc/init.d/couchdb \
# install latest elixir package
&& apt-get install -y elixir erlang-dev erlang-dialyzer erlang-parsetools \
# clean up after ourselves
&& apt-get clean
# install the Phoenix Mix archive
RUN mix archive.install --force https://github.com/phoenixframework/archives/raw/master/phoenix_new.ez
RUN mix local.hex --force \
&& mix local.rebar --force
# install Node.js (>= 8.0.0) and NPM in order to satisfy brunch.io dependencies
# See http://www.phoenixframework.org/docs/installation#section-node-js-5-0-0-
RUN curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - && sudo apt-get install -y nodejs
WORKDIR /code
COPY . /code
RUN mix deps.get && npm install && npm cache clean --force
EXPOSE 80 4000 4001