此章节为《Docker开发指南》的第五章—— 在开发中使用docker 的个人读书笔记
本章节内容如下:
- 先创建如下目录结构
- 构建Hello World应用
- 绑定挂载(bind mount)
- 添加uWSGI
- 指定用户运行程序
- 善用配置文件和辅助脚本
- 通过Compose实现自动化
- 使用Compose的工作流程
想要创建一个简单的Hello World的Web应用程序
1. 先创建如下目录结构
identidock/
└── app
└── identidock.py
# ps:可以用tree命令查看某一目录结构
tree identidock/
如果没有tree命令,可以通过install命令,如
# ubuntu
apt-get install tree
# Redhat/CentOS
yum install tree
# MacOS
brew install tree
identidock.py中的内容为如下:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return "Hello World!\n"
if __name__ == '__main__':
app.run(debug=True,host='0.0.0.0')
以上代码的含义是:
- 对Flask初始化,建立一个人应用程序对象
- 创建一个制定URL的route,当这个URL收到请求时候,函数hello_world被调用
- 初始化Web服务器,这里用0.0.0.0
在identidock目录下创建Dockerfile,内容如下:
FROM python:3.4
RUN pip install Flask==0.10.1
WORKDIR /app
COPY app /app
CMD ["python","identidock.py"]
2. 构建Hello World应用
cd identidock
docker build -t identidock
'''
Sending build context to Docker daemon 3.584kB
Step 1/5 : FROM python:3.4
3.4: Pulling from library/python
f49cf87b52c1: Pull complete
7b491c575b06: Pull complete
b313b08bab3b: Pull complete
51d6678c3f0e: Pull complete
09f35bd58db2: Pull complete
bf6320e45c26: Pull complete
37e455739c2f: Pull complete
faaf9f6fee47: Pull complete
Digest: sha256:ad36d0d99389948d3ebf47740d8cff26e46f725fa3a315a653ff4a842721edba
Status: Downloaded newer image for python:3.4
---> 17929a018b26
Step 2/5 : RUN pip install Flask==0.10.1
---> Running in 4d146d9c3047
Collecting Flask==0.10.1
Downloading Flask-0.10.1.tar.gz (544kB)
Collecting Werkzeug>=0.7 (from Flask==0.10.1)
Downloading Werkzeug-0.13-py2.py3-none-any.whl (311kB)
Collecting Jinja2>=2.4 (from Flask==0.10.1)
Downloading Jinja2-2.10-py2.py3-none-any.whl (126kB)
Collecting itsdangerous>=0.21 (from Flask==0.10.1)
Downloading itsdangerous-0.24.tar.gz (46kB)
Collecting MarkupSafe>=0.23 (from Jinja2>=2.4->Flask==0.10.1)
Downloading MarkupSafe-1.0.tar.gz
Building wheels for collected packages: Flask, itsdangerous, MarkupSafe
Running setup.py bdist_wheel for Flask: started
Running setup.py bdist_wheel for Flask: finished with status 'done'
Stored in directory: /root/.cache/pip/wheels/b6/09/65/5fcf16f74f334a215447c26769e291c41883862fe0dc7c1430
Running setup.py bdist_wheel for itsdangerous: started
Running setup.py bdist_wheel for itsdangerous: finished with status 'done'
Stored in directory: /root/.cache/pip/wheels/fc/a8/66/24d655233c757e178d45dea2de22a04c6d92766abfb741129a
Running setup.py bdist_wheel for MarkupSafe: started
Running setup.py bdist_wheel for MarkupSafe: finished with status 'done'
Stored in directory: /root/.cache/pip/wheels/88/a7/30/e39a54a87bcbe25308fa3ca64e8ddc75d9b3e5afa21ee32d57
Successfully built Flask itsdangerous MarkupSafe
Installing collected packages: Werkzeug, MarkupSafe, Jinja2, itsdangerous, Flask
Successfully installed Flask-0.10.1 Jinja2-2.10 MarkupSafe-1.0 Werkzeug-0.13 itsdangerous-0.24
---> 1df249ca6b11
Removing intermediate container 4d146d9c3047
Step 3/5 : WORKDIR /app
---> 5375ff27929b
Removing intermediate container 8788e4bc91aa
Step 4/5 : COPY app /app
---> 1ed911ed49c0
Step 5/5 : CMD python identidock.py
---> Running in ee6a3a632d37
---> 4e57748b28e5
Removing intermediate container ee6a3a632d37
Successfully built 4e57748b28e5
Successfully tagged identidock:latest
'''
docker run -d -p 5000:5000 identidock
#d0a15b29e165e8042c8fb0afff7c8118917362eef011d1e2a900650d42d83512
测试
curl localhost:5000
#Hello World!
如果是Mac/Window的Docker Machine来运行的话
curl $(docker-machine ip default):5000
#Hello World!
目前流程的问题:即使代码少许改变,也要重新创建镜像,并且启动容器
3. 绑定挂载(bind mount)
docker stop $(docker ps -lq)
#0c75444e8f5f
docker rm $(docker ps -lq)
停止并删除上次运行的容器(加入刚刚的例子不是最后一个运行的容器)
docker rum -d -p 5000:5000 -v "$PWD"/app:/app identidock
通过docker ps找出它的ID,代码挂载到/app,并且启动一个新的容器,-v 绝对路径,$PWD节省输入字数,提高可移植性
验证
curl localhost:5000
#Hello World!
通过sed将World直接替换为Docker
#linux下为sed -i 's/World/Docker/' app/identidock.py
#加入''保证在Unix可以执行(Unix sed -i强制备份)
sed -i '' s/World/Docker/ app/identidock.py
curl localhost:5000
#Hello Docker!
是的,docker一定程度上代替了virtualenv的功能,virtualenv是一个隔离多个python环境的工具,但是仍可以在docker容器后使用virtualenv
4. 添加uWSGI
uWSGI是一个可直接用于生产环境的应用服务器,可以部署在Web服务器(例如nginx)后,使用uWSGI而不是Flask作为Web服务器可以让你的容器更加灵活,适用于各种环境,只需改动Dockerfile的2行就可以将容器过度到uWSGI:
FROM python:3.4
RUN pip install Flask==0.10.1 uWSGI==2.0.8
WORKDIR /app
COPY app /app
CMD ["uwsgi","--http","0.0.0.0:9090","--wsgi-file","/app/identidock.py",\
"--callable","app","--stats","0.0.0.0:9191"]
以上内容包括:
- 添加uWSGI到python pip安装列表
- 创建一个uWSGI命令,启动监听9090端口的http服务器,并从/app/identidock.py运行app,还早9191端口定义了一个数据统计服务器,其实我们还可以在运行docker run时候重新定义CMD内容
docker build -t identidock .
docker run -d -p 9090:9090 -p 9191:9191 indetidock
curl localhost:9090
#Hello Docker
问题:程序不是在命令行执行的,localhost:9191看不到;以root身份运行服务器,存在安全漏洞
5. 指定用户运行程序
添加以下#add标注的内容:
FROM python:3.4
#add 1
RUN groupadd -r uwsgi && useradd -r -g uwsgi uwsgi
RUN pip install Flask==0.10.1 uWSGI==2.0.8
WORKDIR /app
COPY app /app
#add 2
EXPOSE 9090 9191
#add 3
USER uwsgi
CMD ["uwsgi","--http","0.0.0.0:9090","--wsgi-file","/app/identidock.py",\
"--callable","app","--stats","0.0.0.0:9191"]
- 添加了uwsgi用户组,用户名
- 显式地声明容器监听端口
- 指定uwsgi用户进行操作
docker build -t identidock .
docker run identidock woami
创建新镜像,并执行(注意,这里将Web服务器的CMD命令替换为whoami,这个命令会返回它在容器运行的名称)
docker run -d -P --name port-test identidock
docker port port-test
curl localhost:32769
-P 随机选择端口,--name声明为port-name
docker port 询问指定name的端口号
通过curl访问
6. 善用配置文件和辅助脚本
在Dockerfile的相同目录下,添加一个cmd.sh的脚本(注意等号左右不能有空格,《Docker开发指南》书上P66有点坑)
#!/bin/bash
set -e
if [ "$ENV"='DEV' ]; then
echo "Running Development Server"
exec python "identidock.py"
else
echo "Running Production Server"
exec uwsgi --http 0.0.0.0:9090 --wsgi-file /app/identidock.py \
--callable app --stats 0.0.0.0:9191
fi
以上代码的内容为:若ENV变量为DEV,那么它将运行调试Web服务器,exec命令的目的是为了避免创建一个新进程,以确保uwsgi进程可以收到所有信号(如SIGTERM),而不是被父进程所拦截。
另:一般pip的安装内容可放在requirements.txt文件,而uWSGI的配置可以放在.ini文件中
接下来需要更新Dockerfile,以下内容包括:
- 将脚本放进容器
- 从CMD指令调用它
FROM python:3.4
RUN groupadd -r uwsgi && useradd -r -g uwsgi uwsgi
RUN pip install Flask==0.10.1 uWSGI==2.0.8
WORKDIR /app
COPY app /app
#add 1
COPY cmd.sh /
EXPOSE 9090 9191
USER uwsgi
#change 1
CMD ["/cmd.sh"]
需要关掉仍在运行的旧容器,下面命令将停止并删除主机上所有容器
# 停止
docker stop $(docker ps -q)
# 删除(请谨慎使用)
docker rm $(docker ps -aq)
现在可以重建附有这个脚本的镜像
chmod +x cmd.sh
docker build -t identidock .
docker run -e "ENV=DEV" -p 5000:5000 identidock
'''
Running Development Server
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 130-199-939
'''
以-e "ENV=DEV"将会得到一个开发服务器,否则得到一个生产环境的服务器
7. 通过Compose实现自动化
Docker Compose在你装工具箱的时候,已经装了
我们在identidock目录下创建一个名为docker-compose.yml的文件,并写入以下内容(书上P68没有缩紧正确,一直报错ERROR: In file './docker-compose.yml', service 'ports' must be a mapping not an array.,查了半天,应该是以下这么写):
identidock:
build: .
ports:
- 5000:5000
environment:
ENV: DEV
volumes:
- ./app:/app
- 第一行声明构建的容器名称,1个YAML文件可以定义多个容器
- build告诉Compose,这个容器的镜像是当前目录(.)的Dockerfile
- ports相当于docker run -p
- environment相当于docker run -e,将变量ENV设为DEV
- volumes相当于docker run -v,将当前目录的app挂载到容器的根目录下
然后,运行
docker-compose up
'''
Building identidock
Step 1/9 : FROM python:3.4
---> 17929a018b26
Step 2/9 : RUN groupadd -r uwsgi && useradd -r -g uwsgi uwsgi
---> Using cache
---> c76ad080b658
Step 3/9 : RUN pip install Flask==0.10.1 uWSGI==2.0.8
---> Using cache
---> 346587bcb96d
Step 4/9 : WORKDIR /app
---> Using cache
---> 92e7a0d285be
Step 5/9 : COPY app /app
---> 88cb5b326607
Step 6/9 : COPY cmd.sh /
---> ccf9abdc80a6
Step 7/9 : EXPOSE 9090 9191
---> Running in 5984985fe2a0
---> 85f12e9d6aa9
Removing intermediate container 5984985fe2a0
Step 8/9 : USER uwsgi
---> Running in 993f145eb935
---> 2df3e353cdec
Removing intermediate container 993f145eb935
Step 9/9 : CMD /cmd.sh
---> Running in 6d5b1f62f4de
---> ee711916b3d7
Removing intermediate container 6d5b1f62f4de
Successfully built ee711916b3d7
Successfully tagged identidock_identidock:latest
WARNING: Image for service identidock was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating identidock_identidock_1 ...
Creating identidock_identidock_1 ... done
Attaching to identidock_identidock_1
identidock_1 | Running Development Server
identidock_1 | * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
identidock_1 | * Restarting with stat
identidock_1 | * Debugger is active!
identidock_1 | * Debugger PIN: 130-199-939
'''
(由于为之前未删除identidock,因此它创建的容器名称叫做identidock_1),在另一新终端下运行测试
curl localhost:5000
得到
Hello Docker!
同时,原本终端会多一行服务端的信息
identidock_1 | 172.17.0.1 - - [15/Dec/2017 15:35:38] "GET / HTTP/1.1" 200 -
当应用程序完毕,可以按Ctrl+C来停止
如要切换到uWSGI服务器,需要修改YAML的environment和ports值。或者专门产生一个新的YAML,并在执行docker-compose使用-f或者COMPOSE_FILE环境指定它。
8. 使用Compose的工作流程
以下是compose常用命令:
command | usage |
---|---|
up | 启动所有在Compose文件中定义的容器,并把日志信息汇集在一起,通常与-d使用在后台运行 |
build | 重新构建由Dockerfile的镜像。除非镜像不存在,否则up命令不会执行构建的动作,因此需要更新镜像时候,便使用这个命令 |
ps | 获得由Compose管理的容器状态信息 |
run | 启动一个容器,并运行一个一次性的命令。被连接的容器会同时启动,除非使用了--no-deps参数 |
logs | 汇集由Compose管理的容器日志,并以彩色输出 |
stop | 停止容器,但不会删 |
rm | 删除已经停止的容器 |
关于完整的命令,请参考Docker网站的信息:
https://docs.docker.com/compose/reference/