1. 概述

在很久以前,如果想要去部署一个app,需要准备一台物理服务器,然后在这台服务器上面安装一个操作系统,然后在操作系统里面去部署这个app。这种方式部署非常慢,而且成本比较高,会有很大的资源浪费,只是部署一个app就要购买一台服务器,同时也难于迁移和扩展。

随着发展出现了虚拟化的技术,虚拟化技术就是基于计算机的基础上,虚拟化一套物理资源,然后基于虚拟的物理资源安装操作系统。这样一台物理机可以部署多个app

每一个虚拟机都是一个完整的操作系统,要给其分配资源,当虚拟机数量增多的时候操作系统本身消耗的资源也会增多。

容器解决了开发和运维之间的矛盾,在开发和运维之间搭建了一个桥梁,是实现devops的最佳解决方案。

容器是对软件和软件依赖的表转化打包,可以实现应用之间的相互隔离,容器是共享同一个OS Kernel, 不同的容器是在OS Kernel上运行的。

容器是在app层面的隔离,虚拟化是在屋里层面做的隔离。虚拟机的实现首先是服务器上创建了虚拟的屋里设备,然后基于虚拟的屋里设备安装操作系统,在操作系统中部署app。容器是在服务器上安装docker,然后docker创建容器,在容器中部署app。所以虚拟机中每个虚拟机有自己独立的操作系统,容器中所有容器共享一个操作系统。

也可以把虚拟化和容器结合使用。在虚拟器当中安装docker

docker是容器技术的一种实现,也就是说容器技术不只有docker

2. 安装

docker2013年退出的,但是在此之前的2004年和2008,容器技术就已经开始在linux中使用了。

docker底层就是基于2008年的LXC实现的。docker分为企业版和社区版,企业版是收费的。

# 移除旧版docker
sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine

curl -fsSL https://get.docker.com

安装依赖包

sudo yum install -y yum-utils

设置阿里云镜像源

sudo yum-config-manager \
    --add-repo \
    https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

更新yum软件源缓存,并安装docker-ce

sudo yum install docker-ce docker-ce-cli containerd.io
# 停止docker
systemctl stop docker
# 启动docker
systemctl start docker
# 查看docker版本, 会分别展示client和server的版本。
docker version
# 验证docker是否安装成功
docker run hello-world

3. 镜像和容器

对于docker的架构和底层技术来讲,随着学习和慢慢理解以后,会对docker的架构和技术会有更深入的了解。

docker是一个platform,提供了一个打包,开发,运行app的平台。这个平台将底层的物理设备和上层的app隔离开了,在docker之上来做事情。

docker engine有一个后台进程,他提供了一个restapi接口,然后docker engine还有一个cli接口,docker是一个cs的架构,cli和后台进程使用过rest来通信的。

通过可以查看docker后台的进程

ps -ef | grep docker

1. Image镜像

Image是分层的每一层都可以添加和删除文件,成为一个新的Image。不同的Image可以共享相同的layerImage是只读的。

查看本机的Image

docker Image ls

可以通过dockerfile文件定义dockerImage,使用docker build来打包一个新的Image

docker build -t 

也可以从Registry中获取一个docker, 比如下载ubuntu:14.04这个Image。这和github类似,可以使用pullhub.docker中拉取Image

docker pull ubuntu:14.04

也可以将自己的docker文件pushdockerhub

2. 自制一个Image

这里以一个简单的Hello World程序为例, 制作一个Image

要运行hello world需要有hello world的程序,这里使用C语言来编写这个程序。

首先需要创建一个目录,这里叫hello-word。然后在这个目录里面创建一个文件,比如叫hello.c

mkdir hello-word
cd hello-word
vim hello.c

然后在这个文件里面书写代码,就是使用printf输出hello world字符串。

# include<stdio.h>

int main() {
    printf("hello world\n");
}

有了这个程序以后需要将它编译成一个2进制文件,编译C语言程序需要使用gcc程序。可以使用yum install gcc安装gcc,还需要安装glibc-static

yum install glibc-static

通过gcc编译hello.c, -o是输出的文件名,这里叫hello

gcc -static hello.c -o hello

这样在hello-word目录下就会多出一个hello的文件,这是一个可执行的文件。直接运行他就会执行。

./hello

接着将这个hello制作成一个Image,首先需要创建一个Dockerfile文件。

cd hello-word
vim Dockerfile

在这个文件的第一行需要书写FROM,表示在什么之上,可以在其他的Image之上,这里是一个baseImage所以在scratch之上安装,表示从头开始。

使用ADD,将hello添加到Image的跟目录中。

接着运行CMD,指定运行的文件,比如这里指定/hello

FROM scratch
ADD hello /
CMD ["/hello"]

这是一个非常简单的dockerfile,有了这个dockerfile以后就可以去build一个Image了。可以通过docker build -t 指定一个tag,比如这里的tagyindong/hello-world, 后面的.表示当前的目录找到dockerfile

docker build -t yindong/hello-world .

执行完毕就构建完了,这里因为有三行,所以会执行step3步。然后通过docker Image ls就可以看到构建的yindong/hello-world了。

可以使用docker history ImageId查看docker的分层。这个id就是docker Image ls中出现的id

这里FROMscratch,所以这个默认不算一层,所以打印出来的只有两层。

docker history id

现在可以去运行docker了,通过docker run yindong/hello-world

# docker run Image名字
docker run yindong/hello-world

这样就可以打印出hello world了。

docker的image其实就是将可执行文件存储起来,运行的时候是共享了宿主机的硬件环境,不过在运行的时候他是独立运行的。

这就是一个简单的Image,实际上nginxmysql都可以做成Image,他们的工作原理和上面演示的也都是一样的。

3. Container

Container是通过Image创建的,也就是说必须有Image,然后通过Image来创建ContainerContainer是在Image的基础上增加了一层Container layer,这层是可读写的。因为之前讲过Image是只读的,Container因为要去运行程序和安装软件等所以他需要可写的空间。

类比面向对象的概念Image就相当于是类,Container就相当于实例。

Image负责app的存储和分发,Container负责运行app

要基于Image创建Container其实也很简单,就是docker run Image的名字就可以了。

可以使用docker container ls查看当前本地正在运行的容器。

docker container ls

docker container ls -a可以查看所有的容器包括运行的和退出的。

docker container ls -a

可以通过docker run -it的方式来交互式的运行Image。也就是可以在这里运行命令,读写文件之类的。实际上进入到了Container里面。并且在执行exit退出容器的时候,之前的操作也会清除。

docker run -it centos
exit
docker --help

3. 命令

docker的命令分为两部分,第一部分是Management Commands第二部分是 Commands

Management Commands是对docker里面的对象进行管理的。

docker --help
# 查看命令
docker Image
docker container
# 查看container列表
docker container ls -a
# 删除container,id可以不用写全
docker container rm id

Commands提供的是一些简便的方法,比如使用docker container ls -a查看container列表。

docker ps -a
# 删除一个container,默认docker的rm就是remove container
docker rm id
# docker Image ls
docker Images
# 删除 Image
docker Image rm id
docker rmi id

4. 删除container

通过docker ps -a会查看到很多的过期container,可以使用docker rm删除,也可以使用docker container ls -aq打印出所有container id, 这相当于使用搜索打印第一列。

docker container ls -aq
docker container ls -a | awk {'print$1'}

有了这个id可以通过docker rm $(docker container ls -aq)删除所有的。

docker rm $(docker container ls -aq)

如果更复杂的情况比如删除所有已经退出的container,通过筛选退出的容器。然后使用docker rm删除掉这些container

# 列出状态为exited的container
docker container ls -f "status=exited" -q
# 删除指定调教的container
docker rm $(docker container ls -f "status=exited" -q)

2. docker container commit命令

这个命令是创建一个container,然后在这个container中发生了一些变化,比如说安装了某个软件,这样的话可以把这个已经改变的container生成一个新的Image,可以简写成docker commit

docker container commit
docker commit

使用docker run -it centos去交互的运行centos。这样就有了一个container

docker run -it centos

在这个container里面去做一些变化,去安装一个vim。

yum install -y vim

安装完成之后退出exit,退出之后通过docker container ls -a找到刚刚运行的container

可以通过docker commit命令将这个container打包成一个Image,这个Image基于centos并且里面安装好了vim

接收的第一个参数是要commitcontainer,第二个参数要ImageREPOSITORYTAG

# docker commit container列表中的name 新Image的名字。
docker commit hardcore_ishizaka yindong/centos-vim

这个新出现的Image大小要比之前的centos大一些,可以使用docker history id来对比一下yindong/centos-vimcentos,可以发现他们有很多相同的layer

这是因为yindong/centos-vim是基于centos的,所以会直接复用centosLayer,在最后一层中大概有150MB的大小,这是因为安装了vim的原因。

这种创建Image的方式并不十分提倡,因为如果把这个Image发布出去别人拿到这个Image并不会知道这个Image怎么产生的,这就会有问题,因为这个Image很可能包含不安全的内容。

大部分情况下还是建议通过Dockerfile的方式创建Image

首先创建一个docker-centos-vim的目录在这个目录里面创建一个Dockerfile

mkdir docker-centos-vim
cd docker-centos-vim
vim Dockerfile

在这个Dockerfile中首先使用FROMcentos。运行yum命令安装vim,需要使用RUN来执行。

FROM centos
RUN yum install -y vim

然后使用docker build命令构建Image。

docker build -t yindong/centos-vim-new .

这里会产生两层,第一层是引用的centos,第二层会创建一个临时的container用来安装vim,然后在将这个临时的container生成Image,完成之后removing掉这个临时的container,这样就创建了一个新的Image

5. Dockerfile

Dockerfile定义了很多的关键字。

1. FROM

用于指定在哪个Image之上去buildImage

可以选择scratch表示从头去只做一个Image

FROM scratch

对于FROM来讲尽量使用官方的Image作为base Image

2. LABEL

定义ImageMetadata信息,比如说版本,作者,描述等信息。

LABEL maintainer="yindong@126.com"
LABEL version="1.0"
LABEL description="This is description"

LABEL定义的Metadata不可缺少,因为对于Image来讲是必须存在一些帮助信息,他像是代码里面的注释,来帮助别人使用。

3. RUN

RUN是非常常用的,因为很多时候需要运行一些命令,一般需要安装一些软件的时候也经常会使用到RUN,每运行一次RUN对于Image来讲都会新生成一层,所以对于RUN来说他的最佳实践中要求为了避免无用分层,合并多条命令成一行。

比如说yum installyum update推荐通过&&合并成一层。为了美观如果&&导致行变得越来越长可以通过反斜线\换行,增加美观。

# 使用反斜线换行
RUN yum update && yum install -y vim \
    python-dev

# 注意清理cache
RUN apt-get update && apt-get install -y perl \
    pwgen --no-install-recommends && rm -rf \
    /var/lib/apt/lists/*

RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'

4. WORKDIR

设定当前工作目录,这个有点像linux里面通过cd去改变目录,在当前的目录下去做一些事情,比如说运行一些程序或者做一些事情,对于workdir来说如果通过workddir访问一个目录,如果没有这个目录,会自动创建这个目录。

WORKDIR /root
WORKDIR /test
WORKDIR deom
RUN pwd # /test/demo

工作中建议使用WORKDIR不建议使用RUN cd

5. ADD 和 COPY

他们的作用很像都是将本地的文件添加到Image里面。不过ADD不光可以添加还可以解压缩。比如将test.tar.gz添加之后会自动的解压缩。

# 将hello文件添加到/跟目录
ADD hello /

如果通过WORKDIR改变了目录,ADD添加的目录是WORKDIR改变之后的目录.

WORKDIR /root
ADD hello test/ # /root/test/hello

大部分情况COPY要比ADD优先去使用,添加远程文件或者目录,多数使用curl或者wget去下载。

6. ENV

设定常量,比如设置MYSQL_VERSION5.6,那么在下面的代码中就可以使用MYSQL_VERSION这个变量了。尽量使用ENV增加可维护性。

ENV MYSQL_VERSION 5.6 # 设置
RUN apt-get install -y mysql-server="${MYSQL_VERSION}" \ # 引用
    && rm -rf /var/lib/apt/lists/*

7. VOLUME 和 EXPOSE

主要用于存储和网络,后面单独来讲。

8. CMD 和 ENTRYPOINT

RUN是执行命令并创建新的Image Layer

CMD是设置容器启动后默认执行的命令和参数

ENTRYPOINT是设置容器启动时运行的命令

这里来对比一下CMDENTRYPOINT,弄懂他们的区别,在此之前需要了解两种格式。第一种称之为shell格式,第二种称之为exec格式。

shell格式将要运行的命令当成一个shell命令来执行。

RUN apt-get install -y vim
CMD echo "hello docker"
ENTRYPOINT echo "hello docker"

execshell的区别是要使用特定的格式来指明要运行的命令和命令的参数。

RUN ["apt-get", "install", "-y", "vim"]
CMD ["/bin/echo", "hello docker"]
ENTRYPOINT ["/bin/echo", "hello docker"]

接着来看下下面这两个Dockerfile, 首先定义了依赖的Image,然后定义了常量name,接着使用ENTRYPOINT运行了一个echo命令,这里的命令传入了一个参数name

FROM centos
ENV name Docker
ENTRYPOINT echo "hello $name"
FROM centos
ENV name Docker
ENTRYPOINT ["/bin/echo", "hello $name"]

实际打包之后运行发现通过exec的方式并不会将$name替换为常量,这是因为shell格式运行命令的时候,他执行的是一个shell,所以执行的时候可以识别到ENV常量,但是exec的格式执行echo的时候他执行的是echo,并不是shell也就无法取得变量。

可以通过指定exec是通过shell方式去执行的,就是在exec中指定bash,然后通过-c将后面的echo$name都作为参数.而且只能是一个命令。

FROM centos
ENV name Docker
ENTRYPOINT ["/bin/base", "-c", "echo hello $name"]

CMD是容器启动的时候默认执行的命令,上面的例子中如果将ENTRYPOINT改为CMD结果是一样的。如果在docker run的时候制定了其它命令,CMD命令会被忽略掉,如果定义了多个CMD,只有最后一个会执行。

FROM centos
ENV name Docker
CMD echo "hello $name"

# 指明运行的命令
docker run -it [Image] /bin/base

ENTRYPOINT是让容器以应用程序或者服务的形式运行,不会被忽略,一定会执行。最佳实践是写一个shell脚本作为entrypoint。比如说启动一个数据库服务。

比如下面这个mongod的脚本,首先他copy了一个sh脚本到bin中,然后docker-entrypoint.sh最为启动脚本。

COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]
EXPOSE 27017
CMD ["mongod"]

实际上在官方的很多Dockerfile中都会使用ENTRYPOINT

6. Image分发

可以通过hub.docker去拉取别人的Image,同样自己的Image也可以发布到hub.docker上。

hub.docker是一个类似github的网站,它里面存储了很多Image,并且拉取Image的时候是不需要登录的。但是当发布Image的时候是必须要登录的。所以需要注册hub.docker的账号。

如果需要将Image发布到hub.docker,这个tag需要时hub.docker账号/名字,否则将会没有权限,因为只能向自己的hubpush Image

首先需要使用docker login去登录,输入用户名和密码。

docker login

# Login successed

push的方法很简单,就是使用docker push命令,参数就是Image的名字。会根据Image的大小不同push的时间也会不同,push成功就会会返回一个id。这时候去hub.docker就可以看到Image了。

docker push yindong/hello-world

发布成功之后任何一个人都可以拉取push的这个Image

docker pull yindong/hello-world

但是这样发布的Image是有问题的,他还缺乏文档。所以与其分享Image不如分享Dockerfile

可以在create里面点击create automated build, 这个是要把docker账号linkgithub上,然后在github上创建一个仓库,然后将本地用于build ImageDockerfile发布的github上,然后hub.dockergithub做一个关联,也就是说githubDockerfile他就会自动去clone获取到这个Dockerfile然后docker.hub的后台服务器会build,这样既提供了Dockerfile,而且Image又是docker服务器build的,安全性有所提升。只需要去维护Dockerfile就可以了,hub.docker会维护打包Image

7. 搭建私有docker hub

如果不想将Image发布到hub.docker, 想要搭建一个私有的docker registrydocker提供了一个Image叫做registry,通过这个Image就可以在本地或者linux服务器搭建一台自己的类似于docker hub,只不过这个docker hub是没有UI界面的,而且他是只供公司内部或者个人使用的docker hub

搭建这个其实是非常简单的, 只需要运行下面这个命令就可以了。通过registry Image去创建一个container,这个container跑起来之后就相当一个web服务器。

docker run -d -p 5000:5000 --restart always --name registry registry:2

然后就可以使用dockerpush 或者 pull 了。

假设有一台linux服务器,在这台机器上执行上面的run代码。

安装之后可以使用telnet尝试访问上面的linux, 如果telnet不存在可以使用yum安装。

telnet xx.xx.xx.xx 5000

正常是可以访问通的。使用私有的docker hub需要将Imagetag修改,之前的tagname部分需要改为私有docker hub服务的ip:port

docker build xx.xx.xx.xx:5000/hello-world .

这样就可以通过docker push去提交xx.xx.xx.xx:5000/hello-world了。不过在push之前需要在/etc/docker的目录下创建daemon.json文件。这个文件中写入受信任的ip域名

{"insecure-registries": ["xx.xx.xx.xx:5000"]}

有了这个以后需要在dockerserver文件中添加一行。

vim /lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd
EnvironmentFile=-/etc/docker/daemon.json # 添加这条
ExecReload=/bin/kill -s HUP $MAINPID

最后重启docker service

service docker restart

这个时候就可以去push Image了。也可以将Image, pull到本地。

8. 停止container

可以使用stop停止container, PORTSID是运行中的容器id

docker stop PORTSID

启动docker的时候可以使用--name命名。

docker run -d --name=demo Image
# 使用名字删除
docker stop demo
# 启动
docker start demo

inspect会显示出container的详细信息,里面包含很多,包括network信息,logs等非常重要。

docker inspect PORTSID

通过docker构建一个工具,测试linux系统的工具。

使用ENTRYPOINT + CMD可以实现参数传递。空的CMD就可以将docker run最后的参数传递给ENTRYPOINT命令中使用。

ENTRYPOINT ["/usr/bin/stress"]
CMD []

对容器的资源进行限制,linux本身资源是有限的,如果运行太多的容器会占用很多,容器会最大的占用主机资源,所以需要对容器进行限制。

9. 资源限制

docker run的时候是可以指定cpu的个数,内存空间的大小。这里来演示一下。

# 指定内存200M
docker run --memory=200M Image
# 限制cpu权重, 也就是占比
docker run --cpu-shares=10 Image

10. 网络

运行一个container,这个Image叫做busybox是一个很小的linuxImage。执行一段无限循环的代码。

docker run -d --name test1 busybox /bin/sh -c "while true; do sleep 3600; done"

使用docker exec进入到这个container里面,执行/bin/sh的命令。

docker exec -it id /bin/sh

ip a

通过ip a查看本地的网络接口,一般会有两个,一个是本地ip一个是newwork。他有mac地址和ip地址。这就是一个网络的namespace

container中的网络和本地是隔离的,每次创建的docker都是网络隔离的。

container之间是可以ping通的,也就是网络是想通的。

# 查看netns列表
ip netns ls
# 删除指定netns
ip netns delete test1
# 创建netns
ip netns add test1
# 查看名称为test1的netnamespace内容。
ip netns exec test1 ip a
# 查看net端口
ip link
# 让端口up起来。单个端口是没办法up起来的,必须要两个才可以。
ip netns exec test1 ip link set dev lo up

这里创建两个test1test2,然后将他们两个连起来。

首先在机器1上添加link

ip link add veth-test1 type veth peer name veth-test2
# 查看的时候可以发现会多出一堆veth链接。
ip link

接着将veth-test1这个接口添加到test1里面去。

ip lint set veth-test1 netns test1

然后将veth-test2添加到test2里面

ip lint set veth-test2 netns test2

已经将linux1机器中的test1test2分别添加了veth-test1veth-test2, 但是他们的状态都是down,并且没有ip地址。

接下来分别给他们配置ip地址,可以通过下面的命令给他们分配地址。就是在test1test2这两个netspace中执行后的命令也就是分配地址。

ip netns exec test1 ip addr add 192.168.1.1/24 dev veth-test1

ip netns exec test2 ip addr add 192.168.1.2/24 dev veth-test2

将这两个端口启动起来,在test1里面执行ip link set dev veth-test1 up

ip netns exec test1 ip link set dev veth-test1 up

ip netns exec test2 ip link set dev veth-test2 up

现在去查看就可以发现test1test2已经up起来了,并且有ip地址。

ip netns exec test1 ip link
# ping一下test2
ip netns exec test1 ping 192.168.1.2

可以使用docker network ls查看当前机器中container的网络。

11. 端口的

本地创建一个nginx的服务器container

docker run --name web -d nginx

docker ps

这里创建的nginxcontainer,所以并不能访问到这个服务器,nginx的这个container他的网络空间是一个独立的netspace,有自己的ip地址,nginx默认启动的是80端口。

可以看下nginx这个containerip地址,可以通过下面这几种命令。

# 默认是链接再bridge上面的
docker network inspect bridge
# 在外面是可以ping通里面的ip的,因为外面是有docker0bridge的,默认container是连在bridge上的。
ping xx.xx.xx.xx
# 使用telnet访问这个ip加端口,nginx默认是80端口, 是可以访问的。
telnet xx.xx.xx.xx 80
# 通过curl这个网站, 这样就会把nginx的界面拉下来。
curl http://xx.xx.xx.xx

不只希望container只在运行的服务上可以访问,希望其他机器也可以访问。可以端口映射出去。也就是把container的端口映射到服务器的端口上。这样在访问127.0.0.1也可以访问到这个docker container

创建container的时候多加一个参数-p, 也就是端口映射,参数格式是容器里:本地, 比如容器里的80映射到这里的80

docker run --name web -d 80:80 -p nginx
# 可以看到ports里面80映射到了80
docker ps

这个时候访问本地的80端口就可以访问到里面的服务了。

curl 127.0.0.1

12. host network none network

创建container的时候使用--networknone, 创建一个test1busybox容器。

docker run -d --name test1 --network none busybox /bin/sh -c "while true; do sleep 3600; done"
# 查看
docker ps

这个容器的ip地址,mac地址都没有,没有任何网络信息。

docker network inspect none

进入到这个容器里面去。可以发现他除了本地的lo以外没有网络接口。

docker exec -it test /bin/sh
ip a

因为他没有网路接口所以他是一个孤立的容器,没有任何方式可以访问到这个容器,除了exec,这样的容器安全性比较高。具体怎么使用我也不知道。

创建一个连接到host的容器,首先删除其他的容器,这里指定networkhost

docker run -d --name test1 --network host busybox /bin/sh -c "while true; do sleep 3600; done"
# 查看
docker network inspect host

可以发现这个容器也没有mac地址和ip地址,进入到这个docker里面的时候使用ip a可以发现是有网络接口的,而且他的网络接口和主机的接口是相同的。

也就是说通过host创建的容器他是没有独立的net space的,他共享了主机的net space, 这样可能会存在端口冲突。

13. 部署复杂的APP

可以将应用拆分部署,比如说redismysql拆分,主应用部署容器,redis部署容器,mysql也部署容器。因为redismysql不是给外部使用的,所以不需要使用-p做端口映射,这样更安全,可以通过容器间网络通信进行访问。

因为redis是在容器里面的,外部不知道这个容器的ip地址,并且也不需要知道他的ip地址,可以通过link的方式通过容器的名字访问容器。也就是说redis的名字和端口是固定的,只需要告诉新容器这个名字和端口就可以了。

启动第二的时候使用link将前一个的名字传入进去。可以使用-e给容器传入一个环境变量。

docker run -d -p 5000:5000 --link redis --name flask-redis -e REDIS_HOST=redis ImageName

比如创建busybox的时候创建一个环境变量YIN,值为yindong

docker run -d --name test1 -e YIN=yindong busybox /bin/sh -c "while true; do sleep 3600; done"

进入这个容器访问一下这个变量。

docker exec it test1 /bin/sh
env # 可以看到设置的YIN=yindong

# os.environ.get('YIN', '127.0.0.1')  py获取变量

14. 多机器之间的容器通信

首先两台机器AB之间是可以通信的,现在A机器上有个01的容器,B机器上有个02的容器,0102是不可以通信的,但是知道AB是可以通信的,所以可以将01的数据放在A传输给B的数据中,传递给B,B再传递给02,同样BA的数据也可以存在02的数据,再通过A给到01,这就实现了通信。这种方式一般称为隧道。VXLAN的方式。

来演示一下多机通信。首先有AB两台机器,每个机器里面有一个容器0102docker除了bridge还存在overlay,可以通过创建overlay的方式进行通信。这里需要依赖一个分布式的存储,因为要确定两台机器中的容器ip不能相同,这里就需要一个分布式的存储,来告诉不同的服务相同的东西,这种分布式的工具还是很多的,这里选用etcd,开源免费。

需要分别在两台机器上安装etcd,安装成功之后将两台机器关联起来,然后开始执行。

A机器上创建overlay, 执行之后局可以在本地发现一个demooverlay

docker network create -d overlay demo

这个时候在B机器也可以看到A机器创建的demo,这就是etcd做的。

接着要在这网络之上创建一个container,链接到这个网络之上。通过busybox,然后给他一个名字叫做test1,通过--net指定网络是demo

sudo docker run -d --name test1 --net demo busybox sh -c "while true; do sleep 3600; done"
# 查看容器
docker ps

B上也创建一个容器

sudo docker run -d --name test2 --net demo busybox sh -c "while true; do sleep 3600; done"
# 查看容器
docker ps

可以查看两个containerip。这样两个container是可以互相ping通的。

docker netword inspect demo

15. Docker的持久化存储和数据共享

1. Data Volume

一般来讲有些容器会产生一些自己的数据,这些数据不想随着container的消失而消失,需要保证数据的安全,这种场景一般用在数据库,比如使用数据库的容器,数据库会产生一些数据,如果container删除了数据库丢失了,那就有问题了,针对这种问题使用Data Volume

之前在讲dockerfile的时候里面有个关键字是volume,这个关键字就是制定容器某一个目录产生的数据要挂载到主机上的某个目录中,并且会创建一个叫做docker volume的对象。

官方docker hubmysqldockerfile中就存在volume关键字。当启动mysql容器的时候就会在linux/var/lib/mysql存放,不会随着container消失而消失。

VOLUME /var/lib/mysql

创建一个mysqlcontainer, 这里传入参数是不使用密码

docker run -d --name mysql1 -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql

docker ps

可以查看指定容器启动日志,方便查看报错信息。

docker logs mysql1

查看volume, 这个volume就是创建mysql的时候产生的,只要Dockerfile中书写了就会创建。

docker volume ls
# 删除
docker valume rm VOLUME_NAME

volumemount到本地的/var/lib/docker/volumes/.../_data

docker volume inspect VOLUME_NAME

每创建一个container就会新增一个目录。并且删除这个container并不会删除掉这个volume目录。这也就实现了数据会丢的问题。

可以给volume取一个别名方便查看。就是在创建的时候添加一个-v参数。将名称改为mysql,前面是要改为的名字,后面是VOLUME的路径。

docker run -d -v mysql:/var/lib/mysql --name mysql1 -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql

这个时候当删除了container,再次创建container的时候,如果-v的名字相同,他们会继续使用之前的数据。

要在Dockerfile里面指定需要持久化数据的路径,这个路径是容器里面的路径,然后在创建容器的时候使用-v将这个路径重新命名

2. Bind Mouting

他和data volume的区别是data volume需要在Dockerfile中定义,Bind Mouting不需要,他只需要在运行容器的时候去指定本地的目录和容器的目录一一对应的关系,通过这种方式可以做一个同步,也就是说本地路径中的文件和运行容器中的路径文件是同步的。

如果本地本地文件做了修改,那么容器中的文件也会做修改,因为他们就是同一个目录的同一个文件。这里演示一下。创建容器的时候使用-v将本地的目录映射到container里面的目录。比如使用pwd将当前目录映射到/usr/share/nginx/html

docker run -d -p 80:80 -v $(pwd):/usr/share/nginx/html --name web nginx

这样两个目录就共享了,无论修改哪一个都会同步到另一个。因为他们就是一个目录。

16. Docker Compose多容器部署

假设创建的应用需要依赖多个container,每次部署的时候都要全部手动启动一遍,停止的时候也需要每台手动停止,这样太麻烦了,Docker Compose就是一个批处理的工具,可以统一启动容器和停止容器。

可以通过这个文件在这个文件中定义app所需要的所有container,定义之后可以通过一条命令搞定所有容器的启动,停止操作。

Docker Compose是一个基于Docker的命令行工具,这个工具可以通过一个yml格式的文件去定义多个容器的docker的应用。有了yml文件之后通过一条命令根据文件中的定义去创建和管理容器。

所以这里最重要的就是这个yml文件,这个文件有一个默认的名字叫做docker-compose.yml, 可以改成自己需要的。

在这个文件中有三个重要的概念,ServicesNetworksVolumes

Docker Componse是有版本的,所以yml文件也存在版本,目前有3个版本,推荐使用version3,不同的版本对应Docker不同的版本。version2只能用于单机,version3可以用于多机,version1已经淘汰了。

1. Services

一个service代表一个container,这个container可以从docker hubImage来创建,或者从本地的Dockerfile build出来的Image来创建。

service的启动类似docker run,可以给其指定networkvolume

比如下面这个services,他的名字叫做db,然后这个dbImage是从docker hub上拉取的postgres:9.4, 接着volumes映射了一个目录到本地,相当于-v db-data:/var/lib/postgresql/data, 定义一个叫做back-tiernetwork

services:
    db: 
        Image: postgres:9.4
        volumes:
            - "db-data:/var/lib/postgresql/data"
        networks:
            - back-tier

这相当于使用下面的命令启动容器

docker run -d --network back-tier -v db-data:/var/lib/postgresql/data postgres:9.4

还有第二种书写方式,这里的Image不是从docker hub上拉取的, 他是在本地build的,这个build目录就是要buildDockerfile。这里他linksdbredis的容器。一般情况下如果连接到同一个bridge中的时候是不需要links的。只有适应默认的th0的时候才需要links

services:
    worker:
        build: ./worker
        links:
            - db
            - redis
        networks:
            - back-tier

2. Volumes

可以在services同级别定义volumes

services:
    db: 
        Image: postgres:9.4
        volumes:
            - "db-data:/var/lib/postgresql/data"
        networks:
            - back-tier
volumes:
    db-data:

3. networks

可以在services同级别定义networks, 他会创建一个dirverbridge的名字叫做back-tiernetwork

services:
    db: 
        Image: postgres:9.4
        volumes:
            - "db-data:/var/lib/postgresql/data"
        networks:
            - back-tier
networks:
    front-tier:
        driver: birdge
    back-tier:
        driver: bridge

这里基于yml文件定义一个wordpress, 这里第一行是声明版本,这里声明3,首先services里面定义了两个service,第一个是wordpress,端口做了一下映射将容器中的80端口映射到本地的8080端口。可以通过enviroment传递环境变量。networks指定了容器要连接的网络是networks中定义的网络。

mysql中的volumes映射成mysql-data, 同样这里的名字是要在volumes创建的。

version: '3'
services:
    wordpress:
        Image: wordpress
        ports:
            - 8080:80
        enviroment:
            WORDPRESS_DB_HOST: mysql
            WORDPRESS_DB_PASSWORD: root
        networks:
            - my-bridge
    mysql:
        Image: mysql
        environment:
            MYSQL_ROOT_PASSWORD: root
            MYSQL_DATABASE: wordpress
        volumes:
            - mysql-data:/var/lib/mysql
        networks:
            - my-bridge

volumes:
    mysql-data:

networks:
    my-bridge:
        driver: bridge

这就是一个比较完整的docker composefile,总体上来说还是比较清晰的。

4. docker-compose

如果要使用docker compose首先需要安装它,他是一个工具,如果使用的是mac或者windows在安装docker的时候他默认是已经安装好了的。

docker-compose --version

linux需要手动安装(https://docs.docker.com/compose/install/)

sudo curl -L "https://github.com/docker/compose/releases/download/1.27.4/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

docker-compose --version

基于yml文件来试验一下。docker-compose up的功能是启动yml中的container。默认yml文件的名字是docker-compose.yml。也可以使用-f指定文件。-d会后台执行,如果不加会打印出日志在控制台debug的时候可以查看, 关闭就会停止也就是调试模式。

docker-compose -f docker-compose.yml up -d

这样就会启动起来两个container

可以使用stop命令停止containerdown命令不但会停止,同时也会移除container, network, volume

# 启动
docker-compose start
# 停止
docker-compose stop

docker-compose ps
# 查看定义的container和依赖的Image
docker-compose Images
# 进入容器,既然怒mysql的base,这里的mysql是services
docker-compose exec mysql base

docker-compose会在创建的容器和network上添加一些前缀,使用的时候直接用定义的名字就可以,他命令的内部会自动转换。

version: '3'
services:
    redis:
        Image: redis
    web:
        build:
            context: .
            dockerfile: Dockerfile
        ports:
            - 8080:5000
        environment:
            REDIS_HOST: redis

5. scale

这是docker-compose新增的一个功能,可以实现docker容器的扩展,也就是相同的容器启动多少个,用来做负载均衡。比如说让web这个容器启动3个。

docker-compose up --scale web=3

但是这里有个问题,启动3个服务他们的端口映射到了一个端口,是有问题的,所以做法是不写端口映射。然他们直接启用自己的80端口。然后通过haproxy工具将做负载。因为知道每个容器的端口,所以也就很容易。使用haproxy负载到所有的80端口服务,然后映射到8080

version: '3'

services:
    redis:
        Image: redis

    web:
        build:
            context: .
            dockerfile: Dockerfile
        environment:
            REDIS_HOST: redis
    lib:
        Image: dockercloud/haproxy
        links:
            - web
        prots:
            - 8080:80
        volumes:
            - /var/run/docker.sock:/var/run/docker.sock

先创建一个

docker-compose up

docker-compose ps

启动3web,会加上之前的1个。所以一共还是3个。

docker-compose up --scale web=3 -d

这样就实现了一个负载均衡。

docker-compose build这个命令是如果配置文件中有些Image是需要build的可以先通过这个命令把这个Image生成出来,这样在up的时候会快一些。如果service里面发生了变化也可以使用这个命令打包新的service再去启动。

docker-compose build

注意的是docker-compose是一个用于本地开发的工具,并不是用于部署生产环境的工具,他只是方便在本地开发看到结果。

17. 容器编排Docker Swarm

所有docker的操作都是在本地操作的,也就是在一台机器上执行的。但是实际情况是应用可能部署在很多台机器上,也就是在一个集群部署应用,这就涉及很多的容器。

那这种情况怎么去管理这么多的容器,怎么增加一些容器,如果一个容器down掉了怎么自动恢复,怎么样动态更新容器而不影响业务, 如何去监控追踪容器状态,如何去调度容器的创建,怎么去保护隐私数据,等等这些问题都需要去处理。

基于这些需要有一套容器的编排系统,在这种情况下Swarm就诞生了,Swarm不是唯一做编排的工具,他是docker的工具。集成在docker里面的,如果想使用它是不需要安装任何东西的。

Swarm中有两种角色,ManagerWorkerManager是大脑而且不止一个,既然不止一个就要进行数据同步,所以内置了一个分布式。Worker就是干活的节点,大部分服务都是部署在Worker中。

1. Service

Swarm中不使用run,使用service,下面来创建一个service, 采用的Imagebusybox

docker service create --name demo busybox sh -c "while true..."

创建之后查看创建的service。使用ps可以看到容器是运行在swarm上面。

docker service ls
# 查看容器
docker service ps demo
# 创建5个,这五个平均分布到clust里面的。他可以确保5是有效的,当有一个down掉会重新起一个替代。
docker service scale demo=5

2. Routing Meth

Internal:ContainerContainer之间的访问通过overlay网络通过VIP虚拟ip进行通信。

Ingress: 如果服务有绑定端口,则此服务可以通过任意swarm节点的相应接口访问。

LVS实现负载均衡,可以自行查看。

转载须知

如转载必须标明文章出处文章名称文章作者,格式如下:

转自:【致前端 - https://madaozhijian.com】 docker介绍和使用  "隐冬"