使用docker一键部署Web应用

前几天借着部署一个小系统的机会尝试了一把docker,把Web应用,数据库,nginx统统都用docker管理。把打包好的代码扔到服务器上一键就部署好了,不需要考虑服务器的环境问题,简直不要太爽。但感觉还是有蛮多坑的,于是做了一个小demo熟悉了一遍。demo在这里,是一个包含了Flask应用,Nginx,MongoDB数据库的Web应用。clone下来直接执行./depoly.sh就能运行了。
接下来总结下其中需要注意的地方。

整体结构

├── db
│   └── dump
├── docker-compose.yml
├── deploy.sh
├── destroy.sh
├── nginx
│   ├── default.conf
│   ├── Dockerfile
│   └── html
│       └── index.html
└── web
    ├── app
    │   ├── app.py
    │   ├── __init__.py
    ├── Dockerfile
    ├── requirements.txt
    └── start.py
  • MongoDB的镜像不需要做任何修改,db目录下存放的只是需要导入的初始化数据。
  • nginx目录下存放的是nginx的配置文件、前端代码和对应的Dockerfile文件。
  • web目录下是Flask app的代码和对应的Dockerfile文件。
  • docker-compose.yml统一管理需要用到的三个容器。

Dockerfile VS docker-compose

整个项目中就是通过这两个文件操作docker的,刚开始接触可能不是很容易理解他们的差别。比如有时只需要一个Dockerfile,而又有时候只需要一个docker-compose.yml。

  • Dockerfile 负责构建镜像,因为很多时候从hub上拉去下来的镜像不够符合我们的要求,需要一些定制化的修改。比如用于运行flask应用的是一个python镜像,我们需要给他安装一些python包。
    # ./app/Dockerfile
    FROM python:2.7
    ADD . /app
    WORKDIR /app
    RUN pip install -r requirements.txt
    

    在这里我们做的只是把文件导入到docker中,然后安装python依赖包。

  • docker-compose 有两个比较重要的作用,一个是操作容器,比如暴露端口、挂载目录实现与宿主机文件同步、管理容器网络、执行命令等。这些功能都可以通过docker run后面跟一堆参数实现,但是通过docker-compose实现会清晰和方便很多。另一个作用是统一管理多个容器,一个项目往往需要同时使用多个容器,你肯定希望用一个docker-compose up启动他们而不是一个一个地去docker run

其实这两者的区别也不是那么清晰,比如上面Dockerfile中导入文件的操作,可以放到compose中变成目录挂载,让宿主机和docker中的文件同步,这样利用Flask的debug模式的热启动,修改宿主机的文件就能立即看到docker中的运行效果了。

docker中的网络

刚开始接触docker的时候可能并不会太注意docker的网络,完全使用默认配置也没什么问题。但是在一个需要多个容器的应用中,不了解docker的网络可能就不知道如何把多个容器串联起来了。
docker服务器启动后会自动创建三个网络:bridge、host、none。

# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
f3193c939ac5        bridge              bridge              local
560e7d180738        host                host                local
e2f3c776cd7c        none                null                local

默认情况下容器启动后都会加入使用bridge模式的bridge网络。在这种模式中,容器使用一个内网IP,并把网关指向docker服务器创建的虚拟网关docker0。

# docker inspect <container ID>
...
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.4",
...

通过docker inspect可以看到我们运行的容器IP和它的网关docker0的IP,在宿主机上可以直接ping这个地址。同一个网络中的容器可以相互通信,而不同网络中的容器由于默认不在同一个子网,不能相互通信。

compose file 版本差异

docker compose file已经发布到第三版,第三版主要增加了docker swarm相关的内容。第二版与第三版比较兼容,第一版与第二版的差别比较大,网上的很多示例都是基于第一版,然而第一版在接下来的release中已经准备废弃了,所以在这里记录下几个第一版与第二版差别较大的地方。

容器互联

在前面对网络的介绍中我们知道,每一个容器都有一个IP,容器之间通过IP地址相互通信。但是通常情况下IP地址都是新建容器的时候自动分配,所以用硬编码的方式将其他容器的地址写到代码里是不行的。
在第一版中,通常使用links环境变量注入。使用links命令会将被连接容器的所有环境变量都传递给连接的容器。比如在web应用中连接数据库,就是用link连接数据库容器:

web:
  build: .
  command: python -u app.py
  ports:
    - "5000:5000"
  volumes:
    - .:/todo
  links:
    - db
db:
  image: mongo:3.0.2

然后在web代码中从环境变量中获取数据库地址:

client = MongoClient(
    os.environ['DB_PORT_27017_TCP_ADDR'],
    27017)

此时web容器中可以获取到所有db容器的环境变量。

在第二版以后,links就是一个即将被弃用的命令了。因为这种注入所有环境变量的方式不太可控,所以建议使用卷共享这种更可控的方式实现环境变量共享。如果容器在同一个网络中,直接使用容器名,就可以实现容器互联。

services:
  web:
    build: ./web
    ports:
      - "5678"
    volumes:
      - /tmp/app
    command: /usr/local/bin/gunicorn -w 2 -b :5678 start:app

  db:
    image: mongo:3.4
    container_name: demo_db
from mongoengine import *
connect(db='demo', host='demo_db')

这里直接使用容器名demo_db就可以连接到MongoDB数据库了。

默认网络

在第一版中,使用docker-compose启动的容器都会默认被加入到bridge网络中,与其他使用docker run启动的容器共用一个网络。但在第二版以后,使用docker-compose启动容器时,会创建一个以docker-compose.yml所在文件夹名为前缀的网络。

docker_deploy_demo# docker network ls
NETWORK ID          NAME                       DRIVER              SCOPE
4a2bd496dc63        bridge                     bridge              local
c3bc371f694d        host                       host                local
10d54b17f5ab        none                       null                local
8beddd847aad        dockerdeploydemo_default   bridge              local

比如这里文件夹名是docker_deploy_demo,所以新建的bridge网络是dockerdeploydemo_default。所以使用其他容器连接docker-compose所管理的容器时,需要指定他们所在的网络。比如操作数据库容器导入数据:

docker run --rm -v $('pwd')/db/dump:/backup --net dockerdeploydemo_default mongo bash -c 'mongorestore /backup --host demo_db:27017'

镜像标签

为镜像打个标签可以方便管理自己创建的镜像,在第二版中,如果镜像是由dockerfile生成,那么可以用image为镜像自定义标签。

 nginx:
    build: ./nginx
    image: demo_nginx
    ports:
      -  "80:80"
    volumes:
      -  ./nginx/html:/usr/share/nginx/html
    container_name: demo_nginx

但是在第一版中,buildimage是不能共存的。

HTTPS

现在使用https已经是Web应用的标配了,在docker中配置https跟真机中并没有太大差别:先获取证书和密钥,再在nginx中配置好证书和密钥。我另外写了一个小demo演示了在docker中配置https,包含了使用letsencrypt的CA认证证书和自签证书。地址在这里

Comments
Write a Comment