@ -1,4 +1,6 @@
## 使用Docker部署应用
## Docker容器详解
Docker是基于Go语言开发的开源应用容器引擎, 遵从Apache Licence 2.0协议, 可以让开发者打包应用以及应用的依赖包到一个可移植的容器中, 然后发布到各种发行版本的Linux系统上。
### Docker简介
### Docker简介
@ -8,7 +10,7 @@
虚拟机( virtual machine) 就是带环境安装的一种解决方案, 它可以在一种操作系统里面运行另一种操作系统, 比如在Windows系统里面运行Linux系统, 在macOS上运行Windows, 而应用程序对此毫无感知。使用过虚拟机的人都知道, 虚拟机用起来跟真实系统一模一样, 而对于虚拟机的宿主系统来说, 虚拟机就是一个普通文件, 不需要了就删掉, 对宿主系统或者其他的程序并没有影响。但是虚拟机通常会占用较多的系统资源, 启动和关闭也非常的缓慢, 总之用户体验并没有想象中的那么好。
虚拟机( virtual machine) 就是带环境安装的一种解决方案, 它可以在一种操作系统里面运行另一种操作系统, 比如在Windows系统里面运行Linux系统, 在macOS上运行Windows, 而应用程序对此毫无感知。使用过虚拟机的人都知道, 虚拟机用起来跟真实系统一模一样, 而对于虚拟机的宿主系统来说, 虚拟机就是一个普通文件, 不需要了就删掉, 对宿主系统或者其他的程序并没有影响。但是虚拟机通常会占用较多的系统资源, 启动和关闭也非常的缓慢, 总之用户体验并没有想象中的那么好。
Docker属于对Linux容器技术的一种封装( 利用了Linux的namespace和cgroup技术) , 它提供了简单易用的容器使用接口, 是目前最流行的 Linux 容器解决方案。Docker将应用程序与该程序的依赖打包在一个文件里面, 运行这个文件, 就会生成一个虚拟容器。程序在这个虚拟容器里运行, 就好像在真实的物理机上运行一样。下图是虚拟机和容器的对比, 左边是传统的虚拟机, 右边是Docker。
Docker属于对Linux容器技术( LXC) 的一种封装( 利用了Linux的namespace和cgroup技术) , 它提供了简单易用的容器使用接口, 是目前最流行的 Linux 容器解决方案。Docker将应用程序与该程序的依赖打包在一个文件里面, 运行这个文件, 就会生成一个虚拟容器。程序在这个虚拟容器里运行, 就好像在真实的物理机上运行一样。下图是虚拟机和容器的对比, 左边是传统的虚拟机, 右边是Docker。
![](./res/docker_vs_vm.png)
![](./res/docker_vs_vm.png)
@ -299,7 +301,7 @@ select user, host, plugin, authentication_string from user where user='root';
接下来我们试一试运行多个容器并让多个容器之间通过网络通信。我们创建4个Redis容器来实现一主三从的主从复制结构。
接下来我们试一试运行多个容器并让多个容器之间通过网络通信。我们创建4个Redis容器来实现一主三从的主从复制结构。
```Shell
```Shell
docker run -d -p 6379:6379 --name redis-master redis redis-server
docker run -d -p 6379:6379 --name redis-master redis
docker run -d -p 6380:6379 --name redis-slave-1 --link redis-master:redis-master redis redis-server --replicaof redis-master 6379
docker run -d -p 6380:6379 --name redis-slave-1 --link redis-master:redis-master redis redis-server --replicaof redis-master 6379
docker run -d -p 6381:6379 --name redis-slave-2 --link redis-master:redis-master redis redis-server --replicaof redis-master 6379
docker run -d -p 6381:6379 --name redis-slave-2 --link redis-master:redis-master redis redis-server --replicaof redis-master 6379
docker run -d -p 6382:6379 --name redis-slave-3 --link redis-master:redis-master redis redis-server --replicaof redis-master 6379
docker run -d -p 6382:6379 --name redis-slave-3 --link redis-master:redis-master redis redis-server --replicaof redis-master 6379
@ -336,9 +338,7 @@ repl_backlog_histlen:1988
#### 运行GitLab
#### 运行GitLab
GitLab是由GitLab Inc.开发的Git仓库管理工具, 具有wiki、问题跟踪、持续集成等一系列的功能, 分为社区版和企业版。通过Docker提供的虚拟化容器, 我们可以安装社区版的Docker, 命令如下所示。
GitLab是由GitLab Inc.开发的Git仓库管理工具, 具有wiki、问题跟踪、持续集成等一系列的功能, 分为社区版和企业版。通过Docker提供的虚拟化容器, 我们可以安装社区版的Docker。因为GitLab需要使用SSH协议进行安全连接, 我们要暴露容器的22端口, 所以可以先将宿主机SSH连接的22端口修改为其他端口( 如: 12345) , 然后再进行后续的操作。
因为GitLab需要使用SSH协议进行安全连接, 我们要暴露容器的22端口, 所以可以先将宿主机SSH连接的22端口修改为其他端口( 如: 12345) , 然后再进行后续的操作。
```Shell
```Shell
vim /etc/ssh/sshd_config
vim /etc/ssh/sshd_config
@ -438,25 +438,78 @@ jackfrued/mywebserver latest 795b294d265a 14 seconds ago 189 MB
Dockerfile使用DSL( Domain Specific Language) 来构建一个Docker镜像, 只要编辑好了Dockerfile文件, 就可以使用`docker build`命令来构建一个新的镜像。
Dockerfile使用DSL( Domain Specific Language) 来构建一个Docker镜像, 只要编辑好了Dockerfile文件, 就可以使用`docker build`命令来构建一个新的镜像。
我们先创建一个空文件夹并在文件夹下创建名为Dockerfile的文件。
我们先创建一个名为myapp的文件夹来保存项目代码和Dockerfile的文件, 如下所示:
```Shell
```Shell
touch Dockerfile
[ECS-root temp]# tree myapp
myapp
├── api
│ ├── app.py
│ ├── requirements.txt
│ └── start.sh
└── Dockerfile
```
```
编辑这个Dockerfile文件添加如下所示的内容。
其中api是Flask项目的文件夹, 其中包括了项目代码、依赖项以及启动脚本等文件, 具体内容如下所示:
app.py文件:
```Python
from flask import Flask
from flask_restful import Resource, Api
from flask_cors import CORS
app = Flask(__name__)
CORS(app, resources={r'/api/*': {'origins': '*'}})
api = Api(app)
class Product(Resource):
def get(self):
products = ['Ice Cream', 'Chocolate', 'Coca Cola', 'Hamburger']
return {'products': products}
api.add_resource(Product, '/api/products')
```
requirements.txt文件:
```INI
flask
flask-restful
flask-cors
redis
gunicorn
```
start.sh文件:
```Shell
```Shell
vim Dockerfile
#!/bin/bash
exec gunicorn -w 4 -b 0.0.0.0:8000 app:app
```
```
> ** 提示**: 需要给start.sh文件以执行权限, 可以使用`chmod 755 start.sh`命令来做到。
Dockerfile文件:
```Dockerfile
```Dockerfile
# version: 0.0.1
# 指定基础镜像
FROM ubuntu:14.04
FROM python:3.7
# 指定镜像的维护者
MAINTAINER jackfrued "jackfrued@126.com"
MAINTAINER jackfrued "jackfrued@126.com"
RUN apt-get update & & apt-get install -y nginx
# 将指定文件添加到容器中指定的位置
RUN echo 'hello, world!' > /usr/share/nginx/html/index.html
ADD api/* /root/api/
EXPOSE 80
# 设置工作目录
WORKDIR /root/api
# 执行命令(安装Flask项目的依赖项)
RUN pip install -r requirements.txt -i https://pypi.doubanio.com/simple/
# 容器启动时要执行的命令
ENTRYPOINT ["./start.sh"]
# 暴露端口
EXPOSE 8000
```
```
我们来解释一下上面的Dockerfile文件。Dockerfile文件通过特殊的指令来指定基础镜像( FROM指令) 、创建容器后需要指定的命令( RUN指令) 以及需要暴露的端口( EXPOSE) 等信息。我们稍后会专门为大家介绍这些Dockfile中的指令。
我们来解释一下上面的Dockerfile文件。Dockerfile文件通过特殊的指令来指定基础镜像( FROM指令) 、创建容器后需要指定的命令( RUN指令) 以及需要暴露的端口( EXPOSE) 等信息。我们稍后会专门为大家介绍这些Dockfile中的指令。
@ -464,7 +517,7 @@ EXPOSE 80
接下来我们可以使用`docker build`命令来创建镜像,如下所示。
接下来我们可以使用`docker build`命令来创建镜像,如下所示。
```Shell
```Shell
docker build -t="jackfrued/webserver " .
docker build -t "jackfrued/myapp " .
```
```
> 提示:上面的命令最后面的`.` 千万不要漏掉了哦, 它表示从当前路径下寻找Dockerfile。
> 提示:上面的命令最后面的`.` 千万不要漏掉了哦, 它表示从当前路径下寻找Dockerfile。
@ -476,34 +529,31 @@ docker images
```
```
```
```
REPOSITORY TAG IMAGE ID CREATED SIZE
REPOSITORY TAG IMAGE ID CREATED SIZE
jackfrued/webserver latest 87d6cb096be2 23 minutes ago 222 MB
jackfrued/myapp latest 6d6f026a7896 5 seconds ago 930 MB
```
```
如果想知道镜像文件是如何创建出来的,可以使用下面的命令。
如果想知道镜像文件是如何创建出来的,可以使用下面的命令。
```Shell
```Shell
docker history jackfrued/webserver
docker history jackfrued/myapp
```
```
```
```
IMAGE CREATED CREATED BY SIZE
IMAGE CREATED CREATED BY SIZE COMMENT
87d6cb096be2 25 minutes ago /bin/sh -c #(nop) EXPOSE 80/tcp 0 B
6d6f026a7896 31 seconds ago /bin/sh -c #(nop) EXPOSE 8000/tcp 0 B
53d3bc3a123e 25 minutes ago /bin/sh -c service nginx start 3 B
3f7739173a79 31 seconds ago /bin/sh -c #(nop) ENTRYPOINT ["./start.sh"] 0 B
10646b63275e 25 minutes ago /bin/sh -c echo 'hello, world!' > /usr/sha... 14 B
321e6bf09bf1 32 seconds ago /bin/sh -c pip install -r requirements.txt... 13 MB
f3e3bf3e998e 25 minutes ago /bin/sh -c apt-get update & & apt-get insta... 34.3 MB
2f9bf2c89ac7 37 seconds ago /bin/sh -c #(nop) WORKDIR /root/api 0 B
c98e22cf5a64 26 minutes ago /bin/sh -c #(nop) MAINTAINER jackfrued "j... 0 B
86119afbe1f8 37 seconds ago /bin/sh -c #(nop) ADD multi:4b76f9c9dfaee8... 870 B
2c5e00d77a67 3 months ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0 B
08d465e90d4d 3 hours ago /bin/sh -c #(nop) MAINTAINER jackfrued "j... 0 B
< missing > 3 months ago /bin/sh -c mkdir -p /run/systemd & & echo '... 7 B
fbf9f709ca9f 12 days ago /bin/sh -c #(nop) CMD ["python3"] 0 B
< missing > 3 months ago /bin/sh -c rm -rf /var/lib/apt/lists/* 0 B
< missing > 3 months ago /bin/sh -c set -xe & & echo '#!/bin/sh' >... 195 kB
< missing > 3 months ago /bin/sh -c #(nop) ADD file:1e01ab604c0cc30... 188 MB
```
```
使用该镜像来创建容器运行Web服务器。
使用该镜像来创建容器运行Web服务器。
```Shell
```Shell
docker run -d -p 80:80 --name mywebserver jackfrued/webserver nginx -g "daemon off;"
docker run -d -p 8000:8000 --name myapp jackfrued/myapp
```
```
如果希望将上面创建的镜像文件放到dockerhub仓库中, 可以按照如下所示的步骤进行操作。
如果希望将上面创建的镜像文件放到dockerhub仓库中, 可以按照如下所示的步骤进行操作。
@ -611,7 +661,7 @@ docker push jackfrued/webserver
USER nginx
USER nginx
```
```
8. **VOLUME** :在创建容器时添加一个数据卷的挂载点。通过数据卷操作可以实现容器间数据的共享和重用,对卷所作的修改可以马上生效而不需要重新启动容器,我们之前创建容器时使用`— volume`参数就是为了实现数据卷的映射操作。
8. **VOLUME** :在创建容器时添加一个数据卷的挂载点。通过数据卷操作可以实现容器间数据的共享和重用,对卷所作的修改可以马上生效而不需要重新启动容器,我们之前创建容器时使用`-- volume`参数就是为了实现数据卷的映射操作。
```Dockerfile
```Dockerfile
VOLUME ["/路径1", "/路径2/子路径2.1/", ...]
VOLUME ["/路径1", "/路径2/子路径2.1/", ...]
@ -638,7 +688,7 @@ docker push jackfrued/webserver
ONBUILD RUN cd /app/src & & make
ONBUILD RUN cd /app/src & & make
```
```
### 容器编排
### 多容器管理
我们的项目可能会使用了多个容器, 容器多了之后管理容器的工作就会变得麻烦。如果要对多个容器进行自动配置使得容器可以相互协作甚至实现复杂的调度, 这就需要进行容器编排。Docker原生对容器编排的支持非常弱, 但是可以通过社区提供的工具来实现容器编排。
我们的项目可能会使用了多个容器, 容器多了之后管理容器的工作就会变得麻烦。如果要对多个容器进行自动配置使得容器可以相互协作甚至实现复杂的调度, 这就需要进行容器编排。Docker原生对容器编排的支持非常弱, 但是可以通过社区提供的工具来实现容器编排。
@ -663,30 +713,31 @@ docker push jackfrued/webserver
2. 使用Docker Compose。
2. 使用Docker Compose。
我们先创建一个名为`composeapp`的文件夹并在该文件夹下创建两个子文件夹`product-service`和`web-site`,如下所示。
我们在刚才的Flask项目中引入缓存, 然后再利用Flask提供的数据接口为前端页面提供数据, 使用Vue.js进行页面渲染并将静态页面部署在Nginx服务器上。项目文件夹结构如下所示:
```Shell
```Shell
mkdir composeapp
[ECS-root ~]# tree temp
cd composeapp
temp
mkdir product-service
├── docker-compose.yml
mkdir web-site
├── html
│ └── index.html
└── myapp
├── api
│ ├── app.py
│ ├── requirements.txt
│ └── start.sh
└── Dockerfile
```
```
我们先在`product-service`文件夹下编写提供数据的API接口程序。
修改后的app.py文件代码如下所示:
```Shell
vim product-service/api.py
```
我们用Flask来实现一个非常简单的数据接口服务程序。
```Python
```Python
from pickle import dumps, loads
from pickle import dumps, loads
from flask import Flask
from flask import Flask
from flask_restful import Resource, Api
from flask_restful import Resource, Api
from redis import Redis
from flask_cors import CORS
from flask_cors import CORS
from redis import Redis
app = Flask(__name__)
app = Flask(__name__)
CORS(app, resources={r'/api/*': {'origins': '*'}})
CORS(app, resources={r'/api/*': {'origins': '*'}})
@ -698,52 +749,18 @@ docker push jackfrued/webserver
def get(self):
def get(self):
data = redis.get('products')
data = redis.get('products')
if not data:
if data:
products = loads(data)
else:
products = ['Ice Cream', 'Chocolate', 'Coca Cola', 'Hamburger']
products = ['Ice Cream', 'Chocolate', 'Coca Cola', 'Hamburger']
redis.set('products', dumps(products))
redis.set('products', dumps(products))
else:
products = loads(data)
return {'products': products}
return {'products': products}
api.add_resource(Product, '/api/products')
api.add_resource(Product, '/api/products')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, debug=True)
```
```
由于上面的项目需要依赖`flask`、 `flask-restful` 等三方库,所以我们再添加一个指明依赖库的文件并将其命名为`requirements.txt`,其内容如下所示。
html文件夹用来保存静态页面, 稍后我们会通一个运行Nginx的容器来向浏览器提供静态页面。index.html文件的内容如下所示:
```Shell
vim product-service/requirements.txt
```
```
flask
flask-restful
flask-cors
redis
```
稍后我们会将上面的接口服务放在一个容器中运行, 为此我们先编写一个Dockerfile文件以便创建对应的镜像, 其内容如下所示。
```Shell
vim product-service/Dockerfile
```
```Dockerfile
FROM python:3
ADD . /root/product-service
WORKDIR /root/product-service
RUN pip install -r requirements.txt
CMD ["python", "api.py"]
```
我们再去到`web-site`目录下创建一个页面, 稍后我们会通一个容器来提供Nginx服务并运行该页面, 而这个页面会访问我们刚才部署的数据接口服务获取数据并通过Vue.js将数据渲染到页面上。
```Shell
vim web-site/index.html
```
```HTML
```HTML
<!DOCTYPE html>
<!DOCTYPE html>
@ -777,68 +794,63 @@ docker push jackfrued/webserver
< / html >
< / html >
```
```
接下来, 我们要通过一个YAML文件来创建三个容器并指明容器之间的依赖关系。
接下来, 我们要通过docker-compose.yml文件来创建三个容器并指明容器之间的依赖关系。
```Shell
vim docker-compose.yml
```
```YAML
```YAML
version: '3'
version: '3'
services:
services:
api-server:
product-service:
build: ./myapp
build: ./product-service
ports:
ports:
- '8000:8000'
- '8000:8000'
links:
links:
- redis-master
- redis-master
web-server:
web-site:
image: nginx
image: nginx
ports:
ports:
- '80:80'
- '80:80'
volumes:
volumes:
- ./web-site:/usr/share/nginx/html
- ./html:/usr/share/nginx/html
redis-master:
redis-master:
image: redis
image: redis
expose:
expose:
- '6379'
- '6379'
```
```
有了这个YAML文件, 我们就可以使用`docker-compose`命令来创建和管理这三个容器,其命令如下所示。
有了这个YAML文件, 我们就可以使用`docker-compose`命令来创建容器运行项目,其命令如下所示:
```Shell
```Shell
docker-compose up
[ECS-root temp]# docker-compose up
Creating network "temp_default" with the default driver
Creating temp_web-server_1 ... done
Creating temp_redis-master_1 ... done
Creating temp_api-server_1 ... done
Attaching to temp_redis-master_1, temp_web-server_1, temp_api-server_1
redis-master_1 | 1:C 05 Dec 2019 11:57:26.828 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
redis-master_1 | 1:C 05 Dec 2019 11:57:26.828 # Redis version=5.0.6, bits=64, commit=00000000, modified=0, pid=1, just started
redis-master_1 | 1:C 05 Dec 2019 11:57:26.828 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
redis-master_1 | 1:M 05 Dec 2019 11:57:26.830 * Running mode=standalone, port=6379.
redis-master_1 | 1:M 05 Dec 2019 11:57:26.831 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
redis-master_1 | 1:M 05 Dec 2019 11:57:26.831 # Server initialized
redis-master_1 | 1:M 05 Dec 2019 11:57:26.831 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
redis-master_1 | 1:M 05 Dec 2019 11:57:26.831 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
redis-master_1 | 1:M 05 Dec 2019 11:57:26.831 * Ready to accept connections
api-server_1 | [2019-12-05 11:57:27 +0000] [1] [INFO] Starting gunicorn 20.0.4
api-server_1 | [2019-12-05 11:57:27 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1)
api-server_1 | [2019-12-05 11:57:27 +0000] [1] [INFO] Using worker: sync
api-server_1 | [2019-12-05 11:57:27 +0000] [8] [INFO] Booting worker with pid: 8
api-server_1 | [2019-12-05 11:57:27 +0000] [9] [INFO] Booting worker with pid: 9
api-server_1 | [2019-12-05 11:57:27 +0000] [10] [INFO] Booting worker with pid: 10
api-server_1 | [2019-12-05 11:57:27 +0000] [11] [INFO] Booting worker with pid: 11
```
```
```
要停止容器的运行,可以使用下面的命令。
Creating network "composeapp_default" with the default driver
Building product-service
```Shell
Step 1/5 : FROM python:3
docker-compose down
---> e497dabd8450
Step 2/5 : ADD . /root/product-service
---> fbe62813d595
Removing intermediate container 6579e845565a
Step 3/5 : WORKDIR /root/product-service
---> 3a722675e3b1
Removing intermediate container 57fc490436ce
Step 4/5 : RUN pip install -r requirements.txt
---> Running in cadc2d0c1b9b
... ...
---> fc747fc11f4a
Removing intermediate container cadc2d0c1b9b
Step 5/5 : CMD python api.py
---> Running in ecbbd2a69906
---> 637e760f2e5b
Removing intermediate container ecbbd2a69906
Successfully built 637e760f2e5b
WARNING: Image for service product-service was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build` .
Creating composeapp_redis-master_1 ... done
Creating composeapp_web-site_1 ... done
Creating composeapp_product-service_1 ... done
Attaching to composeapp_redis-master_1, composeapp_web-site_1, composeapp_product-service_1
... ...
```
```
#### Kubernetes
实际的生产环境中常常需要部署和管理多个协同工作的容器, docker compose解决了多容器创建和管理的问题, 但是实际项目中, 我们还需要Kubernetes( 以下都简称为K8S) 来提供一个跨主机集群的容器调度平台。K8S可以进行自动化容器的部署、扩展和操作, 从而提供以容器为中心的基础架构。该项目是谷歌在2014年启动的项目, 建立在谷歌公司十余年运维经验的基础之上, 而且谷歌自己的应用也是运行在容器上的。