总结docker知识点

一、安装

centos安装docker

kubeadm部署kubernetes集群

#虚拟机准备
#关闭防火墙和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种类型的卷挂载方式:

  1. bind挂载:就是通过docker run -v /hostdir:/containerdir IMAGE_NAME这种方式来指定宿主机上的路径与容器中的路径

  2. 命名卷:通过docker volume create VOLUME_NAME命令创建的卷,实际路径在/var/lib/docker/volumes下面,通过这种方式创建出来的卷通过卷名就可以挂载,例如docker run -v VOLUME_NAME:/containerdir IMAGE_NAME,直接通过-v参数指定一个名称也会自动创建

  3. 匿名卷:通过docker run -v /containerdir IMAGE_NAME没有指定宿主机目录,或者在Dockerfile文件的VOLUME指令指定的卷,默认会在宿主机的/var/lib/docker/volumes下创建一个以hash串命名的目录

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 启动容器

docker run

# 添加 -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

  1. 推送之前先在dockerhub注册一个账号,然后创建一个仓库,仓库中会有推送的示例命令

     # 创建仓库后dockerhub上的示例
     docker push kevinzhong/nginx-test:tagname
    
  2. 主机上通过docker login登陆注册好的账号

    docker login后不需要跟参数,默认登陆的就是dockerhub站点

  3. 给现有的镜像打一个tag

     # 注意这个tag的格式为  用户名/仓库名:tag
     docker tag nginx:v3 kevinzhong/nginx-test:v3
    
  4. 推送

     docker push kevinzhong/nginx-test:v3
    
     # 推送之后可以通过以下命令来搜索到刚push上去的镜像
     docker search kevinzhong
    

5.1.1 [推荐方式]dockerhub关联github账号实现commit后自动构建镜像

通过dockerhub账号管理github账号,有提交时进行自动构建

5.2 搭建私有仓库

官方docker-registry仓库

Nexus3.x 的私有仓库(推荐)

六、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

docker-network.png

绿色小方块就是虚拟网络,成对工作,就像是现实网络的两个水晶头,这样实现了两个容器之间的访问,同时通过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
overlayarch.png

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

参考

Docker — 从入门到实践

RUN vs CMD vs ENTRYPOINT

Dockerfile - ENV與ARG參數

What Is The Difference Between Binding Mounts And Volumes While Handling Persistent Data In Docker Containers?

play with docker

docker-samples