总结docker知识点
一、安装
#虚拟机准备
#关闭防火墙和selinux
systemctl stop firewalld && systemctl disable firewalld
sed -i 's/^SELINUX=enforcing$/SELINUX=disabled/' /etc/selinux/config && setenforce 0
#关闭swap
swapoff -a
yes | cp /etc/fstab /etc/fstab_bak
cat /etc/fstab_bak |grep -v swap > /etc/fstab
二、镜像操作
2.1 拉取镜像
docker pull ubuntu:18.04
执行docker pull命令的时候如果出现超时无法拉取镜像,那添加国内镜像地址
vim /etc/docker/daemon.json
{
"registry-mirrors": ["http://f1361db2.m.daocloud.io"]
}
systemctl restart docker
2.2 运行镜像
docker run -it --rm ubuntu:18.04 bash
docker run --name my_ubuntu -it --rm -v /abc:/abc ubuntu:18.04 bash
docker run --name webserver -d -p 9090:80 nginx
- -i:交互式操作
- -t:终端
- –rm:退出容器之后自动删除,避免浪费空间
- bash:执行的shell命令
- -v(–volume):[主机目录,省略则代表匿名卷(/var/lib/docker/volumes/xxxxxx)]:<容器目录>:[权限],将主机的目录挂载到容器中,内外目录操作同步容器目录>
- –mount:与上面的-v类似,区别是:-v会自动创建目录,而–mount不会,使用–mount如果目录不存在则会报错
docker中有3种类型的卷挂载方式:
bind挂载:就是通过
docker run -v /hostdir:/containerdir IMAGE_NAME
这种方式来指定宿主机上的路径与容器中的路径命名卷:通过
docker volume create VOLUME_NAME
命令创建的卷,实际路径在/var/lib/docker/volumes
下面,通过这种方式创建出来的卷通过卷名就可以挂载,例如docker run -v VOLUME_NAME:/containerdir IMAGE_NAME
,直接通过-v参数指定一个名称也会自动创建匿名卷:通过
docker run -v /containerdir IMAGE_NAME
没有指定宿主机目录,或者在Dockerfile文件的VOLUME
指令指定的卷,默认会在宿主机的/var/lib/docker/volumes
下创建一个以hash串命名的目录
- -d:后台运行
- -p:主机端口:容器端口
- –restart:设置docker容器的重启策略
2.3 列出现有镜像
docker image ls
docker image ls -a
# 列出现有的虚悬镜像列表
docker image ls -f dangling=true
- -a:列出所有镜像,包括顶级镜像、中间镜像
- -f:代表-filter
docker image ls -f since=mongo:3.2 docker image ls -f before=mongo:3.2
- -q:只显示数字id
- –format:根据指定格式列出镜像列表
docker image ls --format "table \t\t"
2.4 删除镜像
# 根据ID来删除镜像
docker image rm <ID>
# 删除所有仓库名为 redis 的镜像
docker image rm $(docker image ls -q redis)
# 删除所有在 mongo:3.2 之前的镜像
docker image rm $(docker image ls -q -f before=mongo:3.2)
# 清除虚悬镜像
docker image prune
三、容器操作
3.1 启动容器
# 添加 -d 参数可以用后台daemon方式启动
docker run <image-name>
# 查看容器,添加-a参数可以查看到已停止的容器
docker ps -a
docker container stop <ID>
docker container restart <ID>
# 查看容器信息
docker inspect <ID>
--memory100M
可以控制内存100M,默认情况下--memory-swap
跟--memory
大小一样
-e ENV_NAME=ENV_VALUE
,指定容器的环境变量
3.2 查看容器打印日志
# 如果容器启动失败,可以直接使用docker logs<ID>来查看错误日志
docker container logs <ID>
3.3 进入容器
- docker attach
不推荐使用这个命令,因为exit之后容器会停止,而且进入容器之后只能看到log,不能输入命令
- docker exec
推荐使用exec命令进入容器,输入exit之后容器还是在后台继续运行
# 进入容器
docker exec -it <ID> sh
# 查看正在运行的进程的stdout, 0:stdin, 1:stdout, 2:stderr
cat /proc/<pid>/fd/1
与
docker exec
命令相似的另外一个命令nsenter
也比较常用
# 通过docker ps拿到容器ID
docker ps -f ancestor=IMAGE_NAME -q
# 通过容器ID获取到容器的PID
docker inspect --format "" CONTAINER_ID
# 上面获取容器PID的方式也可以合并成一个步骤
docker ps -a | grep IMAGE_NAME | awk '{print $1}'
# 通过PID进入到容器中
nsenter --target $PID --mount --uts --ipc --net --pid
3.4 删除容器
# -f参数可以删除一个正在运行的容器
docker container rm -f <ID>
# 清理终止状态的容器
docker container prune
四、定制镜像
4.1 通过commit命令定制镜像(不推荐)
# 1. 通过nginx镜像启动一个容器,名称为webserver
docker run --name webserver -d -p 9090:80 nginx
# 2. 进入到webserver容器里面,修改nginx的主页信息内容
docker exec -it webserver bash
root@3729b97e8226:/# echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
root@3729b97e8226:/# exit
# 3. 根据容器ID或容器名称,利用commit命令生成新的镜像
docker commit --author "zhongqigang" --message "修改nginx主页" webserver nginx:v2
4.2 通过Dockerfile定制镜像(推荐)
通过Dockerfile来定制镜像的过程示例:
# 1. 创建目录及Dockerfile文件
$ mkdir mynginx
$ cd mynginx
$ touch Dockerfile
# 2. 编写Dockerfile内容
FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
# 3. 构建镜像
docker build -t nginx:v3 .
# 4. 运行镜像
docker run --name mywebserver -d -p 9090:80 nginx:v3
最后的.表示构建当前目录(当前上下文路径),并不一定是Dockerfile所在的目录,默认会在上下文路径下面寻找Dockerfile,也可以通过
-f ../Dockerfile.txt
这种方式来指定Dockerfile文件docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给Docker引擎
示例
通过python+flask创建一个最基本的web应用app.py,构建成docker来执行
# 1. 创建目录及Dockerfile文件
mkdir flask-demo
cd flask-demo
vim app.py
------------------------------
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return "hello docker"
if __name__ == '__main__':
app.run(
host='0.0.0.0',
port=8080,
debug=True
)
------------------------------
# 2. 编写Dockerfile
vim Dockerfile
------------------------------
FROM python:2.7
LABEL maintainer="qigangzhong@hotmail.com"
RUN pip install flask
COPY app.py /app/
WORKDIR /app
EXPOSE 8080
ENTRYPOINT ["python","app.py"]
CMD []
------------------------------
# 3. 构建镜像
docker build -t flask-demo .
docker image ls
# 4. 运行镜像
docker run -d -p 8080:8080 flask-demo
# 5. 查看flask-demo镜像启动的这个容器的运行日志
docker ps -f ancestor=flask-demo -q
docker container logs 3a62879f4af8
# 6. 进入到容器里面查看应用的进程
docker exec -it 3a62879f4af8 bash
ps -ef|grep app
4.3 Dockerfile命令解读
COPY/ADD
COPY命令仅仅复制文件,ADD可以对压缩的源文件进行自动解压
可以利用COPY命令从其它镜像中复制文件
# 在多阶段构建镜像时,从上一个构建阶段的镜像复制文件
COPY --from=0 /go/src/github.com/go/helloworld/app .
# 也可以从任意镜像复制文件
COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
ENV/ARG
ENV命令指定构建时的变量
ARG命令也可以指定构建时的变量,但是它可以指定默认值,这个值可以在
docker build
构建镜像时通过--build-arg
参数来覆盖
CMD/ENTRYPOINT
CMD命令是
docker run
容器启动时默认执行的命令, 如果docker run
命令之后添加了其它参数,会覆盖掉CMD的内容,如果指定了多个CMD,只有最后一个会被执行ENTRYPOINT命令让容器以应用程序或者服务的形式运行,
docker run
容器启动参数会附加到后面当做ENTRYPOINT的参数可以写一个shell脚本作为entrypoint
COPY test-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["test-entrypoint.sh"]
VALUME
Dockerfile中的VALUME命令与
docker run
-v
参数的作用类似都是挂载本机的目录到容器中,内外的两个目录的操作是实时同步的注意:在Dockerfile里面通过VALUME命令指定的容器目录仅仅是一个声明,对应主机目录是一个随机分配的目录(在/var/lib/docker/volumes下面),这样在运行时如果用户不指定主机挂载目录,应用也可以正常运行,不会向容器存储层写入大量数据。如果在
docker run -v
命令中指定的容器目录与Dockerfile VOLUME
指定的一样,则以docker run -v
指定的为准
VOLUME /data
#docker run将主机目录mydata挂载到容器的/data目录下
docker run -d -v mydata:/data xxxx
多个容器可以通过
--volumes-from
参数来共享主机的目录,可以单独创建一个镜像,不需要启动,仅供容器之间共享主机目录用
#容器test1、test2、test3共享镜像myimage挂载的主机目录
docker run --name test1 -it myimage /bin/bash
docker run --name test2 -it --volumes-from test1 ubuntu /bin/bash
docker run --name test3 -it --volumes-from test1 myimage /bin/bash
docker中有3种类型的卷挂载方式,通过以下命令可以查看volume的信息
docker volume ls
docker volume inspect <VOLUME ID/NAME>
EXPOSE
EXPOSE命令仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射,通过
docker run -P
命令进行随机端口映射时会默认映射到这个声明的端口上,或者通过小写的-p <宿主端口>:<容器端口>
来指定实际要映射的端口
WORKDIR/USER
WORKDIR指定工作目录,建议使用绝对路径
USER用来切换到指定用户(必须事先创建好)
RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN [ "redis-server" ]
如果在执行命令的过程中想切换用户,建议使用gosu
# 建立 redis 用户,并使用 gosu 换另一个用户执行命令
RUN groupadd -r redis && useradd -r -g redis redis
# 下载 gosu
RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.7/gosu-amd64" \
&& chmod +x /usr/local/bin/gosu \
&& gosu nobody true
# 设置 CMD,并以另外的用户执行
CMD [ "exec", "gosu", "redis", "redis-server" ]
4.4 多阶段构建(Multi-stage builds)
Docker 17.05版本以后,允许在Dockerfile中使用多个FROM指令,基于不同的镜像进行构建,可以将一个阶段的文件复制到另外一个阶段(见COPY命令说明),在最终阶段的镜像中保留需要的文件。
第一个FROM指令从0开始,可以通过as来取别名
4.5 使用Dockerfile注意事项
Union FS 是有最大层数限制的,比如 AUFS,曾经是最大不得超过 42 层,现在是不得超过 127 层。Dockerfile中的每一个命令都是一层,所以要尽量减少执行的命令,可以利用
&&
将多个命令串起来当做一个命令
# 反例
FROM debian:stretch
RUN apt-get update
RUN apt-get install -y gcc libc6-dev make wget
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install
# 正确的做法
FROM debian:stretch
RUN buildDeps='gcc libc6-dev make wget' \
&& apt-get update \
&& apt-get install -y $buildDeps \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
&& mkdir -p /usr/src/redis \
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
&& make -C /usr/src/redis \
&& make -C /usr/src/redis install \
&& rm -rf /var/lib/apt/lists/* \
&& rm redis.tar.gz \
&& rm -r /usr/src/redis \
&& apt-get purge -y --auto-remove $buildDeps
一般来说,应该会将 Dockerfile 置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎,那么可以用 .gitignore 一样的语法写一个 .dockerignore,该文件是用于剔除不需要作为上下文传递给 Docker 引擎的。
五、推送镜像到仓库
5.1 推送镜像到dockerhub
-
推送之前先在dockerhub注册一个账号,然后创建一个仓库,仓库中会有推送的示例命令
# 创建仓库后dockerhub上的示例 docker push kevinzhong/nginx-test:tagname
-
主机上通过docker login登陆注册好的账号
docker login后不需要跟参数,默认登陆的就是dockerhub站点
-
给现有的镜像打一个tag
# 注意这个tag的格式为 用户名/仓库名:tag docker tag nginx:v3 kevinzhong/nginx-test:v3
-
推送
docker push kevinzhong/nginx-test:v3 # 推送之后可以通过以下命令来搜索到刚push上去的镜像 docker search kevinzhong
5.1.1 [推荐方式]dockerhub关联github账号实现commit后自动构建镜像
通过dockerhub账号管理github账号,有提交时进行自动构建
5.2 搭建私有仓库
六、docker网络知识
介绍
容器之间如何进行互相访问?容器与外部主机及主机外部网络如何进行通信?
# 列出这台机器上docker有哪些网络
docker network ls
--------------------------------------------------------------------
NETWORK ID NAME DRIVER SCOPE
e2697de3efa7 bridge bridge local
b5c442f5f592 host host local
8efc78dc241b none null local
--------------------------------------------------------------------
# 查看有哪些docker容器连接到这个网络上了(container节点下)
docker network inspect <NETWORK ID OR NAME>
# 在主机上通过以下命令查看主机的所有网络
# docker0是个bridge网络,vethxxx@xxx是虚拟网络,用来连接docker0这个bridge
ip a
# 通过这个命令来查看主机有哪些bridge网络(需要主机安装bridge-utils)
# 这个命令也可以查看到有哪些虚拟网络连接到这个bridge网络上了
brctl show
--------------------------------------------------------------------
bridge name bridge id STP enabled interfaces
docker0 8000.0242b791adb8 no vethc822c83
vethdc641f4
vethe48c9d6
vethe502479
--------------------------------------------------------------------
# 通过以下命令来查看docker内部的网络
# 会发现docker里面存在一个eth0@xxx的网络,这个网络和主机上的vethxxx@xxx是一对,docker通过这一对虚拟网络来访问docker0这个bridge网络,从而实现外网访问
docker exec <CONTAINER ID> ip a
绿色小方块就是虚拟网络,成对工作,就像是现实网络的两个水晶头,这样实现了两个容器之间的访问,同时通过NAT实现了外网访问
link(使用很少)
docker容器的ip地址可能是不固定的,可以通过link的方式来通过容器名称互相访问,而不是通过ip地址
docker run -d --name test1
docker run -d --name test2 --link test1
# 这样在test2容器中通过test1就可以访问网络,例如ping test1,但是反过来从test1中ping test2不通
创建自定义bridge网络
# 1. 创建自定义bridge
docker network ls
# -d(--driver)
docker network create -d bridge my-bridge
docker network ls
brctl show
# 2. 容器启动时指定自定义的bridge网络
docker run -d --name flaskdemo --network my-bridge -p 8080:8080 flask-demo
brctl show
docker network ls
docker network inspect 0cc4aa708eca
# 3. 通过docker network connect命令将我们自定义的bridge网络和docker容器连接起来,一个docker容器可以同时连接多个bridge
# 启动另外一个容器flaskdemo2,使用默认的docker0这个bridge
docker run -d --name flaskdemo2 -p 8082:8080 flask-demo
# 将第一步创建的自定义bridge与这个容器连接起来
# 可以看到默认的bridge和自定义的my-bridge中都存在flaskdemo2这个容器
docker network connect my-bridge flaskdemo2
docker network inspect my-bridge
docker network inspect bridge
#由于flaskdemo和flaskdemo2这两个容器都连接到了my-bridge这个网络上了,所以从flaskdemo上可以直接通过ping flaskdemo2连接
docker exec flaskdemo ping flaskdemo2
docker exec flaskdemo2 ping flaskdemo
# 同时,可以看到flaskdemo2容器里面有2个网络,分别连接docker的bridge以及my-bridge这2个网络
docker exec -it flaskdemo2 ip a
多机器通信
假设有2台机器(192.168.255.134,192.168.255.135)做集群,通过这篇教程来创建一个etcd集群,创建完etcd集群之后,两台机器重启docker,并指定cluster-store
# 192.168.255.134
service docker stop
dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --cluster-store=etcd://192.168.255.134:2379 --cluster-advertise=192.168.255.134:2375 &
# 192.168.255.135
service docker stop
dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --cluster-store=etcd://192.168.255.135:2379 --cluster-advertise=192.168.255.135:2375 &
在第一台机器上执行创建一个overlay类型的网络,然后在第二台机器上查看网络列表,发现两台机器的overlay网络是同步的,就是通过etcd实现的同步
# 192.168.255.134
docker network create -d overlay my-overlay
docker network ls
# 192.168.255.135
docker network ls
------------------------------------------------------------------
NETWORK ID NAME DRIVER SCOPE
7f1bfb66bb15 bridge bridge local
b5c442f5f592 host host local
0cc4aa708eca my-bridge bridge local
2f22167edf0d my-overlay overlay global
8efc78dc241b none null local
------------------------------------------------------------------
可以查看到etcd里面的网络数据
# 将etcdctl api的版本切换为2
vim /etc/profile
---------------------
export ETCDCTL_API=2
---------------------
source /etc/profile
# 可以看到有一个docker节点,下面是network和nodes信息
./etcdctl ls /docker/network
./etcdctl ls /docker/nodes
# 通过inspect命令可以看到,暂时这个网络下面还没有任何容器
docker network inspect my-overlay
创建一个容器,指定使用刚才创建的overlay网络
# 192.168.255.134
docker run -d --name flaskdemo3 --network my-overlay -p 8088:8080 flask-demo
# 192.168.255.135
# 如果这个时候在第二台机器上创建一个相同名称的容器,会报错,同一个网络下容器名称不能重复
# docker: Error response from daemon: endpoint with name flaskdemo3 already exists in network my-overlay.
docker run -d --name flaskdemo3 --network my-overlay -p 8089:8080 flask-demo
用不一样的名字启动第二个机器上的容器,查看ip地址,与my-overlay上容器的地址是一致的,并且这两个容器通过ip是可以互相ping通的,通过容器名称同样可以互相ping通
进入容器执行
ip a
会发现除去lo本地回环网络,有两个网络eth0(10.0.0.x)、eth1(172.18.x.x),并且在容器外面通过docker network ls
会发现多了一个名称为docker_gwbridge的网络,10.0.0.x连接的是自定义的overlay网络,172.18.x.x连接的是docker-gwbridge这个网络,用来与主机物理网络联通,图片来自docker lab
docker network ls
docker network inspect 2f22167edf0d
docker exec flaskdemo3 ip a
docker exec flaskdemo4 ip a
# 在192.168.255.135上的容器flaskdemo4尝试ping 192.168.255.134机器上的容器flaskdemo3,通过ip和容器名称都可以ping通
docker exec flaskdemo4 ping 10.0.0.2
docker exec flaskdemo4 ping flaskdemo3
七、多容器部署
需求:启动一个web容器+redis容器,web容器访问redis容器操作redis缓存
第一种方式,首先启动redis容器,然后启动web容器,指定–link redis,这样web容器里面就可以通过redis:6379来联通redis缓存服务器了,由于redis容器仅仅是提供给这个web容器使用的,不需要指定-p进行对外端口映射,web容器需要外部访问需要进行-p端口映射
docker-compose(适合本地开发多容器部署,不适合生产)
- services
- networks
- volumes
安装
sudo curl -L "https://github.com/docker/compose/releases/download/1.24.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
QuickStart从多个镜像启动多个容器
示例:wordpress+mysql
version: '3'
services:
wordpress:
image: wordpress
ports:
- 8080:80
depends_on:
- mysql
environment:
WORDPRESS_DB_HOST: mysql
WORDPRESS_DB_PASSWORD: root
networks:
- my-bridge
mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: wordpress
volumes:
- mysql-data:/var/lib/mysql-data
networks:
- my-bridge
volumes:
mysql-data:
networks:
my-bridge:
driver: bridge
通过命令启动
docker-compose up
docker-compose ps
docker-compose stop
docker-compose start
# 删除容器、网络等
docker-compose down
# 所使用的image
docker-compose images
# 进入容器
docker-compose exec <SERVICE NAME> [COMMAND]
示例Dockerfile构建的方式启动多个容器
通过Dockerfile构建python的web应用,应用连接了一个redis服务器
首先创建app.py
from flask import Flask
from redis import Redis
import os
import socket
app = Flask(__name__)
redis = Redis(host=os.environ.get('REDIS_HOST', '127.0.0.1'), port=6379)
@app.route('/')
def hello():
redis.incr('hits')
return 'Hello Container World! I have been seen %s times and my hostname is %s.\n' % (redis.get('hits'),socket.gethostname())
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)
创建Dockerfile
FROM python:2.7
COPY . /app
WORKDIR /app
RUN pip install flask redis
EXPOSE 5000
CMD [ "python", "app.py" ]
创建docker-compose.yml
version: "3"
services:
redis:
image: redis
web:
build:
context: .
dockerfile: Dockerfile
ports:
- 8080:5000
environment:
REDIS_HOST: redis
启动并访问
docker-compose up
curl http://192.168.255.134:8080/
扩容、负载均衡
创建app.py
from flask import Flask
from redis import Redis
import os
import socket
app = Flask(__name__)
redis = Redis(host=os.environ.get('REDIS_HOST', '127.0.0.1'), port=6379)
@app.route('/')
def hello():
redis.incr('hits')
return 'Hello Container World! I have been seen %s times and my hostname is %s.\n' % (redis.get('hits'),socket.gethostname())
if __name__ == "__main__":
app.run(host="0.0.0.0", port=80, debug=True)
创建Dockerfile
FROM python:2.7
COPY . /app
WORKDIR /app
RUN pip install flask redis
EXPOSE 80
CMD [ "python", "app.py" ]
创建docker-compose.yml
version: "3"
services:
redis:
image: redis
web:
build:
context: .
dockerfile: Dockerfile
environment:
REDIS_HOST: redis
lb:
image: dockercloud/haproxy
links:
- web
ports:
- 8080:80
volumes:
- /var/run/docker.sock:/var/run/docker.sock
启动并访问
docker-compose up -d
curl http://192.168.255.134:8080/
扩容,观察负载均衡情况
docker-compose --scale web=3 up
docker-compose ps
docker-compose up --scale web=3 -d
docker-compose ps
#访问10次,可以看到3个容器被负载访问
for i in `seq 10`;do curl 127.0.0.1:8080; done
复杂应用的部署示例
docker swarm
基础用法
准备3台机器的集群
- 192.168.255.134:作为node manager
- 192.168.255.135:worker
- 192.168.255.136:worker
# 在node manager上执行swarm初始化
docker swarm init --advertise-addr=192.168.255.134
# 在两个worker上执行join
docker swarm join --token SWMTKN-1-2zt7tjpzlcimif1xeh2338xc21rexra8ym8egtgbdbspk4wv6n-f2mfdpnq0s1x8v2146o6e5xbm 192.168.255.134:2377
# 在node manager上查看集群节点状态
docker node ls
# node manager上创建一个service(也就是一个container)
docker service create --name demo busybox sh -c "while true;do sleep 3600;done"
# 查看service列表
docker service ls
# 查看容器详情
docker service ps demo
# 水平扩展
docker service scale demo=5
docker service ls
docker service ps demo
# 在worker机器上可以看到扩展后正在运行的容器
docker ps
# 在其中一个worker机器上删除一个容器
docker rm -f ea343ae34b67
# 在node manager上会发现容器又被重新创建在了其它机器上,集群会保证指定数量的节点在运行
docker service ls
docker service ps demo
# node manager删除service
docker service rm demo
# 示例复杂的应用场景:wordpress+mysql(这个时候就不需要使用etcd来协调了)
## 上面只是创建一个单独的容器扩展到多台机器上,如果创建多个配合工作的容器,容器之间如何通信呢,还是通过overlay网络的方式
## 首先在node manager上创建一个overlay网络,这个网络不会自动分发到worker机器上
docker network create -d overlay demo
docker network ls
# 创建一个mysql的service(注意参数中指定网络为刚才创建的overlay网络)
docker service create --name mysql --env MYSQL_ROOT_PASSWORD=root --env MYSQL_DATABASE=wordpress --network demo --mount type=volume,source=mysql-data,destination=/var/lib/mysql-data mysql:5.7
docker service ls
docker servie ps mysql
# 再创建一个wordpress的service(注意参数中指定网络为刚才创建的overlay网络)
docker service create --name wordpress -p 80:80 --env WORDPRESS_DB_PASSWORD=root --env WORDPRESS_DB_HOST=mysql --network demo wordpress
# 当查看分发的机器的网络时会发现worker机器上同步了node manager机器上的overlay网络
docker service ps wordpress
# 虽然这个时候wordpress仅仅分发到一个机器上了,但是从集群中任何一个机器都可以通过同样的端口进行访问
http://192.168.255.134
http://192.168.255.135
http://192.168.255.136
RoutingMesh(52、53)???
service之间互相访问、负载均衡
dns+vip+iptables+lvs
从外部访问service、负载均衡
compose file v3
通过docker-compose.yml文件来deploy应用集群
首先编写docker-compose.yml
version: '3'
services:
web:
image: wordpress
ports:
- 8080:80
environment:
WORDPRESS_DB_HOST: mysql
WORDPRESS_DB_PASSWORD: root
networks:
- my-network
depends_on:
- mysql
deploy:
mode: replicated
replicas: 3
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
update_config:
parallelism: 1
delay: 10s
mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: wordpress
volumes:
- mysql-data:/var/lib/mysql-data
networks:
- my-network
deploy:
mode: global # 设置为global,不允许scale
placement:
constraints:
- node.role == manager # 只允许部署到manager节点上
volumes:
mysql-data:
networks:
my-network: # 网络必须是overlay,默认其实就是overlay
driver: overlay
执行发布命令
docker stack deploy wordpress --compose-file=docker-compose.yml
docker stack ls
docker stack ps wordpress
docker stack services wordpress
# 访问8080端口访问wordpress站点是否正常
http://192.168.255.134:8080
# 可以进行容器扩展
docker service scale wordpress_web=2
# 停止并删除service、network
docker stack rm wordpress
docker secret
secret(密码,ssh key等)被存储在swarm manager节点的Raft database里面,secret可以assign给一个service,这个service就可以看到这个secret,在container内部secret看起来像文件,但实际是在内存中
mkdir secret-demo
cd secret-demo
# 创建一个password文件,里面存储密码admin123
cat >> password << EOF
admin123
EOF
# 创建一个docker secret
docker secret create my-pw password
# 另外一种创建的方式是从标准输入
echo "admin123" | docker secret create my-pw2 -
# 创建完之后删除password文件,防止密码泄露
rm -rf password
# 查看已创建的secret
docker secret ls
# 删除my-pw2
docker secret rm my-pw2
# 创建service的时候指定使用哪个secret
docker service create --name demo --secret my-pw busybox sh -c "while true;do sleep 3600;done"
# 查看启动的容器service在哪个节点上,进入容器
docker service ls
docker service ps demo
docker exec -it CONTAINER_ID sh
# 可以查看到secret
cat /run/secrets/my-pw
# 示例创建mysql容器时使用secret的方法
docker service create --name db --secret my-pw -e MYSQL_ROOT_PASSWORD_FILE=/run/secrets/my-pw mysql:5.7
docker service ls
docker service ps demo
docker exec -it CONTAINER_ID sh
cat /run/secrets/my-pw
# 输入密码看是否能正常进入mysql
mysql -u root -p
在docker stack的docker-compose.yml文件中使用secret,还是使用上面 compose file v3 章节的docker-compose.yml文件改造
version: '3'
services:
web:
image: wordpress
ports:
- 8080:80
secrets: # 指定事先已经创建好的secret
- my-pw
environment:
WORDPRESS_DB_HOST: mysql
WORDPRESS_DB_PASSWORD_FILE: /run/secrets/my-pw # 指定数据库密码文件
networks:
- my-network
depends_on:
- mysql
deploy:
mode: replicated
replicas: 3
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
update_config:
parallelism: 1
delay: 10s
mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/my-pw
MYSQL_DATABASE: wordpress
volumes:
- mysql-data:/var/lib/mysql-data
networks:
- my-network
deploy:
mode: global
placement:
constraints:
- node.role == manager
volumes:
mysql-data:
networks:
my-network:
driver: overlay
# 如果secret没有事先定义好,可以通过下面的方式从文件生成(示例中使用当前目录下面的password文件存储密码)
# 这种方式不建议,还是容易泄露密码,建议事先创建好secret
# secrets:
# my-pw:
# file: ./password
更新集群
docker swarm中的service更新通过这个命令来更新:docker service update
通过compose file v3 deploy到stack上的集群,只需要更新docker-compose.yml文件,重新执行deploy命令
docker stack deploy NAME --compose-file=docker-compose.yml
UCP-DTR
UCP(Universal Control Panel)
DTR(Docker Trusted Registry)
八、jenkins+docker+maven+git+java
需要给jenkins账号加入到docker群组里面:
sudo usermod -a -G docker jenkins
jenkins示例构建脚本:
#!/bin/bash
#build and package project
mvn clean package -Dmaven.test.skip=true
IMAGE_NAME='docker-test'
CONTAINER_NAME='docker-test-container'
APP_PORT=9090
#stop and remove existing containers
docker rm $(docker stop $(docker ps -a -q --filter ancestor=$IMAGE_NAME --format="")) && echo "container of image $IMAGE_NAME removed" || echo "container of image $IMAGE_NAME does not exist or fail to delete"
#build image
docker build -t docker-test .
#run image
docker run --name $CONTAINER_NAME -p $APP_PORT:$APP_PORT -d docker-test
#clear dangling images
DANGLING_IMAGE_LIST=$(docker images -f "dangling=true" -q)
if [ ! -n "$DANGLING_IMAGE_LIST" ]; then
echo "no images need to clean up"
else
docker rmi $DANGLING_IMAGE_LIST
echo "clear succeeded"
fi
#enter container
#CPID=`docker ps -a | grep docker-test | awk '{print $1}'`
#nsenter --target $CPID --mount --uts --ipc --net --pid
九、容器监控
[TODO]Weave Scope