什么是Docker?
Docker是一个开源项目,诞生于2013年初,最初是dotCloud公司内部的一个业余项目。它基于Google公
司推出的Go语言实现。 项目后来加入了Linux基金会,遵从了Apache 2.0协议,项目代码在GitHub上进行
维护。
Docker自开源后受到广泛的关注和讨论,以至于dotCloud公司后来都改名为Docker Inc。Redhat已经在其
RHEL6.5中集中支持Docker;Google也在其PaaS产品中广泛应用。
Docker项目的目标是实现轻量级的操作系统虚拟化解决方案。
Docker的基础是Linux的容器技术(LXC)。
我们知道,传统的虚拟机通过在宿主主机中运行hypervisor来模拟一整套完整的硬件环境提供给虚拟机的操
作系统。虚拟机系统看到的环境是可限制的,也是彼此隔离的。 这种直接的做法实现了对资源最完整的封
装,但很多时候往往意味着系统资源的浪费。 例如,以宿主机和虚拟机系统都为Linux系统为例,虚拟机中运
行的应用其实可以利用宿主机系统中的运行环境。
我们知道,在操作系统中,包括内核、文件系统、网络、PID、UID、IPC、内存、硬盘、CPU等等,所有的资
源都是应用进程直接共享的。 要想实现虚拟化,除了要实现对内存、CPU、网络IO、硬盘IO、存储空
间等的限制外,还要实现文件系统、网络、PID、UID、IPC等等的相互隔离。 前者相对容易实现一些,后
者则需要宿主机系统的深入支持。
随着Linux系统对于名字空间功能的完善实现,程序员已经可以实现上面的所有需求,让某些进程在彼此隔
离的名字空间中运行。大家虽然都共用一个内核和某些运行时环境(例如一些系统命令和系统库),但是
彼此却看不到,都以为系统中只有自己的存在。这种机制就是容器(Container),例如名字空间来做权限的
隔离控制,利用cgroups来做资源分配。而Docker,正是在容器的基础上进行了进一步的封装,让用户不需要
去关心容器的管理,使得操作更为简便。用户操作Docker的容器就像操作一个快速轻量级的虚拟机一样简单。
下面的图片比较了Docker和传统虚拟化方式的不同之处,可见容器是在操作系统层面上实现虚拟化,直接
复用本地主机的操作系统,而传统方式则是在硬件层面实现。
为什么要使用docker?
作为一种新兴的虚拟化方式,Docker跟传统的虚拟化方式相比具有众多的优势。
首先,Docker容器的启动可以在秒级实现,这相比传统的虚拟机方式要快得多。
其次,Docker对系统资源的利用率很高,一台主机上可以同时运行数千个Docker容器。
而且容器除了运行其中应用外,基本不消耗额外的系统资源,使得应用的性能很高,同时系统的开销尽量小。
Docker可以实现快速的应用交付
对开发人员来说,最希望的就是一次创建,可以在任意地方执行。 对运维人员来说,最希望的就是一次配
置,可以在任意环境运行。
开发者可以使用一个标准的镜像来构建一套开发容器,开发完成之后,运维人员可以直接使用这个容器来部署代码。
Docker可以快速创建容器,快速迭代应用程序,并让整个过程全程可见,使团队中的其他成员更容易理解应用程序
是如何创建和工作的。
Docker容器很轻!很快!容器的启动时间是次秒级的,大量地节约开发、测试、部署的时间。更容易的部署和扩展
对运维人员来说,最希望的就是一次配置,可以在任意环境运行。
Docker容器可以在几乎所有的环境中运行,物理机、虚拟机、公有云、私有云、个人电脑、服务器等等。
Docker容器兼容很多平台,这样就可以很容易地把一个应用程序从一个平台迁移到另外一个。虚拟化的层次决定了
效率
Docker容器的运行不需要额外的hypervisor支持,它是内核级的虚拟化,因此可以实现更高的性能和效率。快速部
署也意味着
更简单的管理通常只需要小小的改变就可以替代以往巨型和大量的更新工作。所有的修改以增量的方式被分发和更
新,可以实现自动化并且高效的管理。
Docker包括三个基本概念
镜像(Image)
容器(Container)
仓库(Repository)
理解了这三个概念,就理解了Docker的整个生命周期。
镜像
docker 镜像就是一个只读的模板。
例如:一个镜像可以包含一个完整的ubuntu的操作系统,里面仅安装了Apache或者你需要的其它应用程
序。
镜像可以用来创建Docker容器。
Docker提供了一个很简单的机制来创建镜像或者更新现有的镜像,你甚至可以直接从其他人那里下载一个
已经做好的镜像来直接使用。
docker容器
Docker利用容器来运行应用。
容器是从镜像创建的运行实例。它可以被启动、开始、停止、删除。每个容器都是相互隔离的、保证安全
的平台。可以把容器看做是一个应用程序。
*镜像是只读的,容器在启动的时候创建一层可写层作为最上层。
docker仓库
仓库是集中存放镜像文件的场所,分为公有仓库和私有仓库2种形式。
公有仓库,目前仅有Docker Hub,提供了一个数量庞大的镜像库供用户下载。当然,用户也可以在本地网
络内创建一个私有仓库。
当用户创建了自己的镜像之后就可以使用push命令将它上传到公有或者私有仓库,这样下次在另外一台机
器上使用这个镜像时候,只需要从仓库上pull下来就可以了。
*Docker Hub的功能跟GitHub类似;push和pull操作跟git的操作类似。
安装部署
安装环境:redhat
[root@foundation27~]yum install -y docker-engine-17.03.1.ce-1.el7.centos.x86_64.rpm
docker-engine-selinux-17.03.1.ce-1.el7.centos.noarch.rpm #此版本过高,有太多依赖性,需要7.3的yum源
[root@foundation27 ~]# systemctl start docker #启动
[root@foundation27 ~]# docker version #查看docker版本
docker run -it --name vm1 ubuntu bash 创建容器
docker ps -a 查看容器状态
docker attach vm1 连接容器
docker top vm1 查看容器进程
docker logs vm1 查看容器指令输出 -f 参数可以实时查看
docker inspect vm1 查看容器详情
docker stats vm1 查看容器资源使用率
docker diff vm1 查看容器修改
docker run -d --name vm1 ubuntu bash -c "while true; do echo westos; sleep 1; done" 后台运行
docker stop vm1 停止容器
docker start vm1 启动容器
docker kill vm1 强制干掉容器
docker restart vm1 重启容器
docker pause/unpause vm1 暂停/恢复容器
docker rm vm1 删除容器
docker export vm1 > vm1.tar 导出容器
docker import vm1.tar image 导入容器为镜像 image
镜像管理
docker search 查询镜像
docker pull 拉取镜像
docker push 推送镜像
[root@foundation27 ~]# docker load -i game2048.tar #导入镜像
[root@foundation27 ~]# docker images game2048 #查看镜像
[root@foundation27 ~]# ip addr #查看docker ip
[root@foundation27 ~]# brctl show
[root@foundation27 ~]# docker run -d --name game game2048 #给容器命名并让其后台启动
[root@foundation27 ~]# docker inspect game #查看容器详细信息找出IP
[root@foundation27 ~]# docker stop game #停止容器
[root@foundation27 ~]# docker rm game #删除容器
[root@foundation27 ~]# docker run -d --name game -p 8080:80 game2048 #做端口映射,使访问物理机8080端口可以访问到
[root@foundation27 ~]# docker load -i ubuntu.tar
[root@foundation27 ~]# docker run --name vm1 -it ubuntu #创建容器 -it 获取交互式shell
此时,容器与物理机共享内核,将主机的文件挂载过来并加以修改
Ctrl+pq 退出交互式,并没有停止容器
[root@foundation27 ~]# docker ps -a #查看容器状态
容器端口映射:
[root@foundation27 ~]# docker run -d --name vm1 -p 8000:80 nginx # -p指定端口 -P 随机分配端口
[root@foundation27 docker]# vim test.html
[root@foundation27 docker]# docker container cp test.html vm1:/usr/share/nginx/html #设置发布页面
数据卷管理
docker run 在创建容器时使用 -v 参数可以挂载一个或多个数据卷到当前运行的容器中,-v的作用是将宿主机上的目录作为容器的数据卷挂载到容器中,使宿主机和容器之间可以共享一个目录。挂载数据卷到新创建的容器上:
# docker run -it --name westos -v /tmp/data1:/data1 -v /tmp/data2:/data2 rhel7 /bin/bash
-v 参数可以重复使用,挂载多个数据卷到容器中,冒号前面的是宿主机的目录(本地目录
不存在 docker 会自动创建),冒号后面的是容器中的挂载目录。
注:docker commit 时卷的数据不会被保存。
默认挂载可以读写数据卷,也可以只读挂载:
# docker run -it --name westos2 -v /tmp/data2:/data2:ro
rhel /bin/bash
挂载宿主机文件:
[root@foundation27 docker]# docker run -d --name vm1 -v `pwd`/web:/usr/share/nginx/html -p 8000:80 nginx
[root@foundation27 docker]# docker run -it --name vm2 -v /etc/passwd:/passwd:ro ubuntu
[root@foundation27 docker]# docker run -it --name vm3 -v /tmp/docker/data1:/data1 -v /tmp/docker/data2:/data2:ro ubuntu
容器互联:
--link 参数可以在不映射端口的前提下为两个容器间建立安全连接, --link 参数可以连接一个或多个容器到将要创建的容器。
--link 参数的格式为 --link name:alias,其中 name 是要链接的容器的名称,alias 是这个连接的别名。
[root@foundation27 ~]# docker run -it --name vm1 ubuntu
[root@foundation27 ~]# docker run -it --name vm2 --link vm1:server1 ubuntu “:”前为要连接容器名,后面为给起的别名
网络管理
Docker 在启动时会创建一个虚拟网桥 docker0,默认地址为 172.17.0.1/16, 容器启动后都会被桥接到 docker0 上,并自动分配
到一个 IP 地址。
容器的四种网络模式:
bridge 桥接模式、host 模式、container 模式和 none 模式启动容器时可以使用 --net 参数指定,默认是桥接模式。
以下是 docker 网络初始化的过程:
Bridge 桥接模式的实现步骤主要如下:
(1) Docker Daemon 利用 veth pair 技术,在宿主机上创建两个虚拟网络接口设备,假设为veth0 和 veth1。而 veth pair 技术的特性
可以保证无论哪一个 veth 接收到网络报文,都会将报文传输给另一方。
(2) Docker Daemon 将 veth0 附加到 Docker Daemon 创建的 docker0 网桥上。保证宿主机的网络报文可以发往 veth0;
(3) Docker Daemon 将 veth1 添加到 Docker Container 所属的 namespace 下,并被改名为 eth0。如此一来,保证宿主机的网络报文
若发往 veth0,则立即会被 eth0 接收,实现宿主机到Docker Container 网络的联通性;同时,也保证 Docker Container 单独使用 eth0,
实现容器网络环境的隔离性。
bridge 桥接模式下的 Docker Container 在使用时,并非为开发者包办了一切。最明显的是,该模式下 Docker Container 不具有一个公有
IP,即和宿主机的 eth0 不处于同一个网段。导致的结果是宿主机以外的世界不能直接和容器进行通信。虽然 NAT 模式经过中间处理实现了这一
点,但是 NAT 模式仍然存在问题与不便,如:容器均需要在宿主机上竞争端口,容器内部服务的访问者需要使用服务发现获知服务的外部端口等。另
外 NAT 模式由于是在三层网络上的实现手段,故肯定会影响网络的传输效率。
Host 网络模式:
host 模式是 bridge 桥接模式很好的补充。采用 host 模式的 Docker Container,可以直接使用宿主机的 IP 地址与外界进行通
信,若宿主机的 eth0 是一个公有 IP,那么容器也拥有这个公有 IP。同时容器内服务的端口也可以使用宿主机的端口,无需额外进行 NAT
转换。当然,有这样的方便,肯定会损失部分其他的特性,最明显的是 Docker Container 网络环境隔离性的弱化,即容器不再拥有隔离、
独立的网络栈。另外,使用 host 模式的 Docker Container 虽然可以让容器内部的服务和传统情况无差别、无改造的使用,但是由于网
络隔离性的弱化,该容器会与宿主机共享竞争网络栈的使用;另外,容器内部将不再拥有所有的端口资源,原因是部分端口资源已经被宿主机本
身的服务占用,还有部分端口已经用以 bridge 网络模式容器的端口映射。
Vm2使用和主机一样的网络
Container 网络模式:
(1) 查找 other container(即需要被共享网络环境的容器)的网络 namespace;
(2) 将新创建的 Docker Container(也是需要共享其他网络的容器)的 namespace,使用
other container 的 namespace。Docker Container 的 other container 网络模式,可以用来更好的服务于容器间的通信。
在这种模式下的 Docker Container 可以通过 localhost 来访问 namespace 下的其他容器,传输效率较高。虽然多个容器共享
网络环境,但是多个容器形成的整体依然与宿主机以及其他容器形成网络隔离。另外,这种模式还节约了一定数量的网络资源。但是需要
注意的是,它并没有改善容器与宿主机以外世界通信的情况。
Vm3使用和vm1一样的ip
None 网络模式:
网络环境为 none,即不为 Docker Container 任何的网络环境。一旦 Docker Container 采用了none 网络模式,那么容器内部
就只能使用 loopback 网络设备,不会再有其他的网络资源。可以说 none 模式为 Docker Container 做了极少的网络设定,但是
俗话说得好“少即是多”,在没有网络配置的情况下,作为 Docker 开发者,才能在这基础做其他无限多可能的网络定制开发。这也恰巧
体现了 Docker 设计理念的开放。
在 none 网络模式下分配固定 ip:netns 是在 linux 中提供网络虚拟化的一个项目,使用 netns 网络空间虚拟化可以在本地虚拟
化出多个网络环境,目前 netns 在 lxc 容器中被用来为容器提供网络。使用 netns 创建的网络空间独立于当前系统的网络空间,其
中的网络设备以及 iptables 规则等都是独立的,就好像进入了另外一个网络一样。
[root@foundation27 ~]# docker run -it --name vm4 --net none ubuntu
[root@foundation27 ~]# ip link add name veth0 type veth peer name veth1
[root@foundation27 ~]# brctl addif docker0 veth0
[root@foundation27 ~]# mkdir /var/run/netns
[root@foundation27 ~]# docker inspect vm4 | grep Pid #查看vm4的Pid
[root@foundation27 ~]# cd /var/run/netns/
[root@foundation27 netns]# ln -s /proc/4396/ns/net 4396
[root@foundation27 netns]# ip addr
[root@foundation27 netns]# ip link set veth1 netns 4396
[root@foundation27 netns]# docker attach vm4
[root@foundation27 netns]# ip netns exec 4396 ip link set veth1 name eth0
[root@foundation27 netns]# ip netns exec 4396 ip link set eth0 up
[root@foundation27 netns]# ip netns exec 4396 ip addr add 172.17.0.100/24 dev eth0
[root@foundation27 netns]# ip netns exec 4396 ip route add default via 172.17.0.1
[root@foundation27 netns]# docker attach vm4
常用的 namespace 的命令:
1. 添加一个 namespace
ip netns add [name]
2. 在 namespace 中启用一个设备
ip netns exec [name] ip link set lo up
3. 在 namespace 中新加一个设备
ip link set [dev-name] netns [name]
启用:
ip netns exec [name] ip link set [dev-name] up
4. 查看指定 namespace 中指定设备的参数信息
ip netns exec [name] ip addr show [dev-name] permanent scope global
5. 为 namespace 中指定设备设置 ip
ip netns exec [name] ip -4 addr add 192.168.1.2/24 brd 192.168.1.255 scope
global dev [dev-name]
6.查看所有 network namespace
ip netns list
7.ping 虚拟机实例
ip netns exec [name] ping 192.168.1.3
Docke 安全
[root@foundation27 docker]# docker run --name vm0 -it ubuntu
[root@foundation27 docker]# docker run --name vm1 -it --privileged=true ubuntu
Dockfile 安装部署服务
Dockerfiel安装apache
[root@foundation27 docker]# vim Dockerfile #编写配置文件
FROM rhel7
MAINTAINER [email protected]
ENV HOSTNAME server1
EXPOSE 80
COPY dvd.repo /etc/yum.repos.d/dvd.repo
RUN rpmdb --rebuilddb &&yum install -y httpd
CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]
[root@foundation27 docker]# docker build -t rhel7:apache . #编译镜像
[root@foundation27 docker]# docker run -d -p 8000:80 --name web -v `pwd`/web:/var/www/html rhel7:apache
Dockerfile部署ssh
[root@foundation27 docker]# cat Dockerfile
FROM rhel7
MAINTAINER [email protected]
ENV HOSTNAME server2
EXPOSE 22
COPY dvd.repo /etc/yum.repos.d/dvd.repo
RUN rpmdb --rebuilddb &&yum install -y openssh-server openssh-clients && ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
-q -N "" && ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key -q -N "" && ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key
-q -N "" && echo root:westos | chpasswd
CMD ["/usr/sbin/sshd", "-D"]
[root@foundation27 docker]# docker build -t rhel7:ssh .
[root@foundation27 docker]# docker run -d --name ssh -p 2222:22 rhel7:ssh
[root@foundation27 ~]# ssh -p 2222 [email protected]
[root@foundation27 docker]# docker inspect ssh #查看容器详情ip
Dockerfile部署多个服务
[root@foundation27 docker]# vim dvd.repo
[root@foundation27 docker]# cat dvd.repo
[dvd]
name=rhel7.3
baseurl=http://172.25.27.250/rhel7.3
gpgcheck=0
[supervisor]
name=supervisor
baseurl=http://172.25.27.250/pub/docker
gpgcheck=0
[root@foundation27 docker]# vim Dockerfile
[root@foundation27 docker]# cat Dockerfile
FROM rhel7
MAINTAINER [email protected]
ENV HOSTNAME server3
EXPOSE 22 80
COPY dvd.repo /etc/yum.repos.d/dvd.repo
RUN rpmdb --rebuilddb &&yum install -y httpd openssh-server openssh-clients supervisor && ssh-keygen -t rsa -f
/etc/ssh/ssh_host_rsa_key -q -N "" && ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key -q -N "" && ssh-keygen
-t ed25519 -f /etc/ssh/ssh_host_ed25519_key -q -N "" && echo root:westos | chpasswd
COPY supervisord.conf /etc/supervisord.conf
CMD ["/usr/bin/supervisord"]
[root@foundation27 docker]# vim supervisord.conf
[supervisord]
nodaemon=true
[program:httpd]
command=/usr/sbin/httpd -DFOREGROUND
[program:sshd]
command=/usr/sbin/sshd -D
[root@foundation27 docker]# docker build -t rhel7:super .
[root@foundation27 docker]# docker run -d --name super -p 2222:22 -p 8000:80 rhel7:super
Docker -compose
[root@foundation27 桌面]# mv docker-compose-Linux-x86_64 docker-compose #重命名二进制命令
[root@foundation27 桌面]# mv docker-compose /bin/ #移动到/bin/下
[root@foundation27 桌面]# cd /bin/
[root@foundation27 bin]# chmod +x docker-compose
[root@foundation27 bin]# which docker-compose
[root@foundation27 bin]# cd /tmp/docker/
[root@foundation27 docker]# mkdir compose/
[root@foundation27 compose]# docker images
[root@foundation27 compose]# vim docker-compose.yml
web1:
image: rhel7:apache
volumes:
- "./web1:/var/www/html/"
expose:
- 80
web2:
image: nginx
volumes:
- "./web2:/usr/share/nginx/html/"
expose:
- 80
haproxy:
image: haproxy
volumes:
- ./haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg
links:
- web1
- web2
ports:
- "8080:80"
expose:
- 80
[root@foundation27 compose]# mkdir haproxy
[root@foundation27 compose]# vim haproxy/haproxy.cfg
global
log 127.0.0.1 local0
log 127.0.0.1 local1 notice
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
stats uri /status
frontend balancer
bind 0.0.0.0:80
mode http
default_backend web_backends
backend web_backends
balance roundrobin
server apache web1:80 check
server nginx web2:80 check
[root@foundation27 compose]# mkdir web1
[root@foundation27 compose]# mkdir web2
[root@foundation27 compose]# cat web1/index.html
[root@foundation27 compose]# cat web2/index.html
[root@foundation27 compose]# docker-compose up
[root@foundation27 ~]# docker ps
测试:
Docker swarm
Server21 22 23先安装docker并启动
[root@server21 ~]# docker swarm init #设置swarm 集群的控制节点
[root@server22 ~]# systemctl start docker
# 在server22 server23上按照提示执行复制srever21内容,使其加入集群
[root@server22 ~]# docker swarm join \
> --token SWMTKN-1-647rxivlg8buev7xq07rupsd4t4chqmrg8qyh6pb8u0x4mw4h9-865466niii48b52d449k1s9tm \
> 172.25.27.21:2377
This node joined a swarm as a worker.
[root@server21 ~]# docker node ls #查看节点
[root@server21 ~]# docker load -i nginx.tar #3台节点都做
[root@server21 ~]# yum install bash -* #是tab 键可以自动补齐命令,装好后必须断开ssh,重新ssh 连接才能使其生效
[root@server21 ~]# docker service create --name web --replicas 3 -p 80:80 nginx #创建容器并命名
[root@server21 ~]# docker service ps web #查看web状态
[root@server21 ~]# yum install -y net-tools
[root@server21 ~]# docker ps #查看web 的信息,复制其id
[root@server21 ~]# vim index.html #编写nginx发布页面
[root@server21 ~]# docker container cp index.html 79d9093ece1d:/usr/share/nginx/html #server22,23 做同样操作
测试:
可以看出此时swarm集群中每个节点都起了以wed服务,并以负载均衡方式提供服务
[root@server23 ~]# systemctl stop docker.service #停止server23上的docker服务
发现,当swarm集群其中一个节点宕掉后,其会自动退出机集群,剩下的节点继续负载均衡
[root@server21 ~]# docker service scale web=6 #设置服务数量
添加图形界面
[root@server21 ~]# docker load -i visualizer.tar #三个节点都导入,否则会监控不到
[root@server21 ~]# docker service create \
> --name=viz \
> --publish=8000:8080/tcp \
> --constraint=node.role==manager \
> --mount=type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock \
> dockersamples/visualizer
灰度更新
[root@foundation27 ~]# docker save rhel7:apache > rhel7.tar #导出镜像,即制作要更新的镜像
[root@server21 mnt]# docker load -i rhel7.tar #21,22,23都导入
[root@server21 mnt]# docker service update --image rhel7:apache --update-delay 5s --update-parallelism 3 web #每5秒更新一次,每次更新3个