容器
官网的介绍是这样的:
Docker is an open platform for developers and sysadmins to build, ship, and run distributed applications....
其实看完这句话也不是很明白究竟是啥,可以把它想象成一个用来一种新颖方式实现的超轻量虚拟机,在大概效果上也是正确的。当然在实现的原理和应用上还是和VM有巨大差别的,并且专业的叫法是应用容器(Application Container)
为啥要用容器
那么应用容器长什么样子呢,一个做好的应用容器长的就好像一个装好了一组特定应用的虚拟机一样。比如我现在想用MySQL那我就找个装好MySQL的容器,运行起来,那么我就可以使用MySQL了。
但是直接装个MySQL不就好了,何必还需要容器这么诡异的概念。话是这么说,可以你要真装MySQL的话可能要再装一堆依赖库,根据你的操作系统平台和版本进行设置,可能还要把配置在重新弄一遍。但是有了容器,你就相当于有了一个可以运行起来的虚拟机,只要你能运行容器,MySQL的配置就全省了。而且一旦你想换台机器,直接把这个容器端起来,再放到另一个机器就好了。硬件,操作系统,运行环境什么的都不需要考虑了。
举个网友的例子,在公司中的一个很大的用途就是可以保证线下的开发环境、测试环境和线上的生产环境一致。网友A在 Baidu 经常碰到这样的事情,开发把东西做好了给测试去测,一般会给一坨代码和一个介绍上线步骤的上线单。结果代码在测试机跑不起来,开发就跑来跑去看问题,一会儿啊这个配置文件忘了提交了,一会儿啊这个上线命令写错了。找到了一个 bug 提上去,开发一看,啊我怎么又忘了把这个命令写在上线单上了。类似的事情在上线的时候还会发生,变成啊你这个软件的版本和我机器上的不一样……在 北现 的时候,由于一个开发直接担任上述三个职位,而且有一套自动化部署的机制所以问题会少一点,但是上线的时候大家还是胆战心惊。
如果利用容器的话,那么开发直接在容器里开发,提测的时候把整个容器给测试,测好了把改动改在容器里再上线就好了。通过容器,整个开发、测试和生产环境可以保持高度的一致。
此外容器也和VM一样具有着一定的隔离性,各个容器之间的数据和内存空间相互隔离,可以保证一定的安全性。
为啥不用VM
那么既然容器和 VM 这么类似为啥不直接用 VM 还要整出个容器这么个概念来呢?Docker 容器相对于 VM 有以下几个优点:
启动速度快,容器通常在一秒内可以启动,而 VM 通常要更久 ;
资源利用率高,一台普通 PC 可以跑上千个容器,你跑上千个 VM 试试 ;
性能开销小, VM 通常需要额外的 CPU 和内存来完成 OS 的功能,这一部分占据了额外的资源。
为啥相似的功能在性能上会有如此巨大的差距呢,其实这和他们设计的理念是相关的。
VM的设计图如下:
VM的Hypervisor需要实现对硬件的虚拟化,并且还要搭载自己的操作系统,自然在启动速度和资源利用率以及性能上有比较大的开销。
而Docker的设计图是这样的:
Docker几乎就没有什么虚拟化的东西,并且直接复用了 Host 主机的 OS,在 Docker Engine 层面实现了调度和隔离重量一下子就降低了好几个档次。 Docker 的容器利用了 LXC,管理利用了 namespaces 来做权限的控制和隔离, cgroups 来进行资源的配置,并且还通过 aufs 来进一步提高文件系统的资源利用率。
其中的 aufs 是个很有意思的东西,是 UnionFS 的一种。他的思想和 git 有些类似,可以把对文件系统的改动当成一次 commit 一层层的叠加。这样的话多个容器之间就可以共享他们的文件系统层次,每个容器下面都是共享的文件系统层次,上面再是各自对文件系统改动的层次,这样的话极大的节省了对存储的需求,并且也能加速容器的启动。
说到这里,大家是不是脑海里已经形成了容器是个什么东东了吧?那接下来就在详细的介绍下目前主流的容器技术Docker。
Docker简介
2013年出现,RedHat在6.5版本开始支持docker,使用go语言开发,基于apache2.0协议,开源软件,项目代码在github维护。
我们具体来看看Docker。
大家需要注意,Docker本身并不是容器,它是创建容器的工具,是应用容器引擎。
想要搞懂Docker,其实看它的两句口号就行。
第一句,是"Build, Ship and Run"。
也就是,"搭建、发送、运行",三板斧。
举个例子:
我来到一片空地,想建个房子,于是我搬石头、砍木头、画图纸,一顿操作,终于把这个房子盖好了。
结果,我住了一段时间,想搬到另一片空地去。这时候,按以往的办法,我只能再次搬石头、砍木头、画图纸、盖房子。
但是,跑来一个老巫婆,教会我一种魔法。
这种魔法,可以把我盖好的房子复制一份,做成"镜像",放在我的背包里。
等我到了另一片空地,就用这个"镜像",复制一套房子,摆在那边,拎包入住。
怎么样?是不是很神奇?
所以,Docker的第二句口号就是:"Build?once,Run?anywhere(搭建一次,到处能用)"。
Docker技术的三大核心概念,分别是:
· 镜像(Image)
· 容器(Container)
· 仓库(Repository)
docker特点
docker容器之间是相互隔离的。
docker 比较轻量,启动非常快,秒级实现。
资源利用率比较高,一台机器可以跑上千个docker容器。
内核级别的虚拟化,不需要额外的hypevisor支持。主机的内核版本跟随母机 kernel 内核版本最低:2.6.32-573.18.1.el6
容易迁移,平台依赖性不强。
更快的交付和部署,一次创建配置,任意地方运行
docker和虚拟机的区别
docker是以进程状态存于系统中
docker容器启动是秒级,虚拟机启动是分钟级。
docker容器的硬盘使用一般为MB,虚拟机一般为GB。
docker单机支持上千个容器,而虚拟机一般有几十个就不错了。
docker核心概念
镜像:是一个只读的模板,类似于安装系统用到的ISO镜像。
容器:容器是镜像产生的一个进程,类似于虚拟机本身。
仓库:存放镜像的一个场所。类似于github的仓库概念。分为公有仓库和私有仓库,本地可以做私有仓库。
最大的公开仓库:docker hub
国内公开仓库:dockerpool
OPENVZ OS模板:openvz.org
docker的启动
centos 6
# yum install -y epel-release
# yum install -y docker-io
# /etc/init.d/docker start
centos 7
# yum install -y docker
# systemctl start docker
Ubuntu 15.10
# sudo apt-get install -y docker-io
# /etc/init.d/docker start
docker镜像管理
查看本地镜像
[root@localhost ~]# docker images
在docker仓库中搜索镜像
[root@localhost ~]# docker search centos
从docker.com获取镜像
[root@localhost ~]# docker pull centos
此时再查看本地镜像
修改镜像名
显示在REPOSITORY列
docker tag 原镜像名 自定义名
[root@localhost ~]# docker tag centos shaohs
修改镜像名+TAG
docker tag 原镜像名 自定义名:自定义TAG名
docker tag shaohs shaohs:first
docker tag IMAGE_ID 自定义名:自定义TAG名
docker tag db0f5936b391 shaohs:second
删除一个镜像
docker rmi 镜像名
docker rmi 镜像名:TAG
如果镜像改过镜像名或TAG,又想用镜像名的方法删除这个镜像,后面必须跟上TAG名
docker rmi IMAGE_ID
当参数是IMAGE_ID时,要注意其他镜像中是否有使用相同IMAGE_ID的其他镜像
用镜像开启容器
docker run -it shaohs:first /bin/bash
用下载的镜像开启容器,-i表示让容器的标准输入打开,-t表示分配一个伪终端,要把-it放到镜像名字前面。
docker ps查看运行的容器
docker ps –a 查看所有容器,包括已经退出的
将修改过内容的容器保存为一个自定义的镜像
先查看系统版本
[root@5d25d4c4e905 /]# cat /etc/redhat-release
此时是不带ifconfig命令的
官方的centos源是默认带yum工具的
下载net-tools
[root@5d25d4c4e905 /]# yum install -y net-tools
之后就可以使用ifconfig命令了
将修改保存成新的镜像
docker commit –m "自定义commit信息" –a "作者名称" CONNTATINER_ID 自定义镜像名称
docker commit -m "haha_centos" -a "shs" 7d05bd04862a shs_centos
基于本地模板导入创建镜像
模板获取,可以直接在网上下载一个模板(以下是openvz的模板,openvz也是一种虚拟化技术,可见openvz的模板在docker上也是可以用的)
下载速度并不快,若我们下载了一个centos的模板centos-6-x86.tar.gz,那么导入该镜像的命令为:
cat centos-6-x86.tar.gz | docker import - centos-6-x86_64
之后就生成了一个镜像,我们用docker images是可以查看的
将镜像导出
docker save -o 自定义文件名.tar 镜像名
docker save -o 自定义文件名.tar IMAGE_ID
会保存在当前目录下
用本地文件恢复镜像
docker load –input 镜像名.tar
docker load < 镜像名.tar
docker load --input mycentos.tar
docker load < mycentos.tar
--input也可以替换成-i,有时候导入的镜像,docker images会显示镜像名和TAG为none,需要用"docker tag IMAGE_ID 自定义镜像名"去修改
上传镜像到dockerhub
docker push IMAGE_NAME
例:docker push mycentos
docker容器管理
启动容器
docker run –it 镜像名 /bin/bash
[root@localhost ~]# docker run -it shs_centos /bin/bash
[root@localhost ~]# docker run -it shs_centos:latest /bin/bash
如果是改过TAG的镜像 要加"镜像名:TAG" 因为默认的TAG为latest
注意:要把选项放到镜像名前
-i表示让容器的标准输入打开
-t表示分配一个伪终端
/bin/bash表示在这个容器里面运行的一个命令,也可以直接写为bash
-d选项直接放到后台运行 如不加-d,exit后,这个容器会关闭
--name指定容器的NAME列显示
例:docker run –itd –name shscentos mycentos:latest bash
-p可以指定容器的映射端口,格式为"-p 宿主机端口:容器端口"
创建一个容器并不启动
docker create –it 镜像名
[root@localhost ~]# docker create -it shaohs:first
启动关闭一个容器
docker start CONTAINER/NAME
docker stop CONTAINER/NAME
CONTAINER_ID和NAME为docker ps –a出来的第一列和最后的NAMES列
也可以同时启动关闭多个:
[root@localhost ~]# docker start evil_ptolemy determined_hawking nostalgic_fermat
进入一个容器
docker exec –it CONTAINER_ID /bin/bash
docker attach CONTAINER_ID
使用attach命令如果推出容器,这个容器同时也会终止。
如果用—name指定了容器名的话,后面也可以跟容器名
例:docker exec –it evil_ptolemy bash
查看正在运行的容器或所有容器
docker ps或docker ps -a
删除一个容器
docker rm CONTAINER_ID/NAME
-f选项可强制删除一个容器,如果这个容器正在运行的话,也可以同时删除多个
例子:docker rm evil_ptolemy determined_hawking nostalgic_fermat
快速删除docker中的容器
停用全部运行中的容器
docker stop $(docker ps -q)
删除全部容器
docker rm $(docker ps –aq)
一条命令实现停用并删除容器
docker stop $(docker ps –q) & docker rm $(docker ps –aq)
启动容器同时运行命令
docker run –d centos bash –c "while :;do echo "123";sleep 1;done"
然后用docker logs container_id就可以查看到输出的123了
查看容器的日志输出信息
docker logs CONTAINER_ID
[root@localhost ~]# docker logs determined_hawking
给容器自定义名字
docker run –name web –itd cento bash
--name 给容器自定义名字
导入导出容器
docker export CONTAINER_ID > file.tar
cat file.tar | docker import – 自定义镜像名
为什么导入时是镜像名呢?因为容器是作为一个镜像导入的,需要在镜像的基础上,再去产生并dock run这个容器。
例子如下:
如果shsaaa的镜像导入不进去,多半是因为下载的镜像有问题,或者docker images看一下,是不是导入不完全的同名镜像,删除,重新导入。
docker仓库管理
下载私有仓库镜像
docker pull registry
registry镜像用来创建私有仓库
依托registry镜像来创建容器d
docker run –d –p 5000:5000 registry
-d 放到后台运行
-p 做一个端口映射,"宿主机端口:容器端口"访问宿主机5000端口,就相当于访问该容器
当docker ps的时候,会在PORTS列显示 0.0.0.0:5000->5000/TCP
用curl 127.0.0.1:5000测试,会显示
"\"docker-registry server\""
上传一个镜像到私有仓库
1)首先要给这个镜像做tag
必须带有私有仓库的ip:port,要不然会上传到公有仓库里面去
[root@localhost ~]# docker tag shsaaa shao:shao
docker tag 镜像名 私有库ip:port/镜像名
[root@localhost ~]# docker tag shao:shao 192.168.1.247:5000/hong
2)更改https为http
为什么要修改为http呢,因为docker从1.3.x之后,与docker registry交互默认使用的是https,然而此处搭建的私有仓库只提供http服务,所以当与私有仓库交互时就会报下面的错误。
docker push 192.168.1.247:5000/centos //此时报错了类似如下Error response from daemon: invalid registry endpoint
https://172.7.15.106:5000/v0/: unable to ping registry endpoint
https://172.7.15.106:5000/v0/v2 ping attempt failed with error: Get
https://172.7.15.106:5000/v2/: EOFv1 ping attempt failed with error: Get
https://172.7.15.106:5000/v1/_ping: EOF. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry 172.7.15.106:5000` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at
/etc/docker/certs.d/172.7.15.106:5000/ca.crt
为了解决这个问题需要在启动docker server时增加启动参数为默认使用http访问。解决办法为:
[root@localhost home]# vim /etc/init.d/docker
将$exec -d $other_args &>> $logfile &
改为$exec -d --insecure-registry 192.168.1.247:5000 $other_args &>> $logfile &
然后重启docker
[root@localhost home]# service docker restart
再启动registry容器
[root@localhost home]# docker start 63cbe2eeab40
63cbe2eeab40为registry_container_id
3)上传镜像
docker push 私有库ip:port的镜像名
[root@localhost home]# docker push 192.168.1.247:5000/centos
4)去私有仓库查看镜像
[root@localhost home]# curl
也可以用docker search命令
[root@localhost home]# docker search 192.168.1.247:5000/cent
拉取私有库的镜像
需要指定私有库的地址端口
[root@localhost home]# docker pull 192.168.1.247:5000/centos
docker数据管理
挂载本地的目录到容器里
docker run -tid -v /data/:/data/ shs_centos:latest bash
-v用来指定挂载目录,:前面的/data/为本地目录,:后面的/data/为容器里的目录
执行完后,可以发现docker容器里/data/目录里和本地/data/目录里是一样的
本地的/data/目录
容器的/data/目录
挂载数据卷
挂载目录的时候,可以指定容器name,如果不指定就随机定义了。比如上面没有指定,他就生成了一个名字为sleepy_tesla,这个铭刻可以使用命令docker ps看最右侧一列。
[root@localhost ~]# docker run -itd --volumes-from sleepy_tesla shaohs:first bash
1cfcec3d65b38fb516a6142c8c5a429a8ccd394c7ce589e147cd8832c11995c8
这样,我们就用shaohs:first镜像创建了新的容器,并且使用了sleepy_tesla容器的数据卷
自定义容器name
[root@localhost ~]# docker run -itd -v /home/:/home/ --name datashs shaohs:first bash
5423d4218c726de9a1da777b9aba897be199ff2d846d1e4b7ed21701cf83b60e
然后docker ps的时候,就会看到NAMW为datashs的容器了
定义数据卷容器
有时候需要多个容器之间相互共享数据,类似于linux里面的NFS。所以就可以搭建一个专门的数据卷容器,然后其他容器直接挂在该数据卷。
首先建立数据卷容器:
[root@localhost ~]# docker run -itd -v /data/ --name shao_testv1 shaohs:first bash
e285a0fe1309ebeb54ed0d332616fe8f9e03fb58f673278d54c88f64654b973a
[root@localhost ~]# docker exec -it e28 bash
[root@e285a0fe1309 /]# ls /data/
注:这里的/data/是容器的/data目录,并非本地的/data/目录
然后将其他容器挂载该数据卷
[root@localhost ~]# docker run -itd --volumes-from shao_testv1 shaohs:first bash
85dee791d2dd4af0c176904b447e89a129318c06b7d270c23642b17f9d2bdb95
[root@localhost ~]# docker exec -it 85d bash
[root@85dee791d2dd /]# ls /data/
数据卷的备份
[root@localhost ~]# mkdir /vol_data_backup
[root@localhost ~]# docker run -itd --name shao_testv2 --volumes-from shao_testv1 -v /vol_data_backup/:/backup shs_centos bash
7025ab69fa7a4cc253e99f87ddff6519977b2924c0f90fb99d5684c07ee68144
说明:首先需要使用shao_testv1数据卷新开一个容器,同时还要把本地的/vol_data_backup/目录挂在到该容器的/backup下,这样在容器中/backup目录里面新建的文件,这就我们就可以直接在/vol_data_backup/目录中看到了。
我们在容器中把/data目录下的文件,压缩到/backup/下
[root@e2e87d0974ac /]# tar cvf /backup/data.tar /data/
tar: Removing leading `/' from member names
/data/
/data/test/
/data/1.txt
[root@e2e87d0974ac /]# ls /backup/data.tar
然后再查看本地的vol_data_backup/目录
[root@localhost ~]# ls /vol_data_backup/data.tar
数据卷的恢复
思路:先新建一个数据卷容器,再建一个新的容器并挂在该数据卷容器。然后再把tar包解包。
新建数据卷容器:
[root@localhost ~]# docker run -itd -v /data/ --name shao_testv7 shaohs:first bash
06dbc9d96ebd730cf93afcb8016537acde2759550205e266f7d67a8635a837b4
挂在数据卷,新建容器
[root@localhost ~]# docker run -itd --name shao_testv8 --volumes-from shao_testv7 -v /vol_data_backup/:/backup shaohs:first bash
23683bceb1e247d8ac1853b051881a4db4181fff7f6cee4ffbff76bba87f02ac
解包:
[root@23683bceb1e2 /]# tar xvf /backup/data.tar
data/
data/test/
data/1.txt
docker网络管理
docker的4种网络模式
host模式:在docker run时,用—net=host指定。这种模式下容器复用了宿主机的网卡,容器内的ip和宿主机的ip是一致的。包括主机名都是一样的。
container模式:使用—net=
container:CONTAINER_ID/CONTAINER_NAME指定。多个容器使用共同的网络。
如下面这个例子:这个新建的test容器就复用了shaohs这个容器的网络模式。ip和test容器的ip是一致的。
例:docker run –it –name=test –container:shaohs centos bash
none模式:使用—net=none指定,这个模式下的容器会不配置任何网络,只有一个lo网卡。
bridge模式:使用—net=bridge指定。模式不指定就是这样模式,类似于VMware的NAT模式。同一个宿主机的容器在一个网段下,之间可以相互通信。
docker网桥
bridge模式是docker默认的网络配置,此模式会为每一个容器分配Network Namespace、设置IP等,并将一个主机上的docker容器连接到一个虚拟网桥上。当docker server启动时,会在主机上创建一个docker0的虚拟网桥,此主机上启动的docker容器会连接到这个虚拟网桥上。
虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。接下来就要为容器分配ip了,docker会从RFC1918所定义的私有IP段中,选择一个和宿主机不同的IP地址和子网分配给docker0,连接到docker0的容器就从这个子网汇总选择一个未占用的IP使用。
如一般docker会使用172.17.0.0/16这个网段,并将172.17.42.1/16分配给docker0网桥(在主机上使用ifconfig命令是可以看到docker0的,可以认为他是网桥的管理接口,在宿主机上作为一块虚拟网卡使用)
列出当前主机网桥
# brctl show
或者
# brctl show docker0
trctl工具依赖bridge-utils包
查看当前docker0 ip
# ifconfig docker0
或者
# ip addr show docker0
自定义docker0网桥的网段
默认情况下docker0会分配172.1.42或者192.168.42这个网段。
我们也可以手动更改这个网段为192.168.10.0/24
要注意顺序:
# /etc/init.d/docker stop #停掉docker服务
# ip link set dev docker0 down #停掉网桥docker0
# ip addr add 192.168.10.1/24 dev docker0 #给docker0添加地址
# ip addr del 192.168.42.1/24 dev docker0 #删除docker0原有的地址
# ip link set dev docker0 up #启动网桥docker0
# /etc/init.d/docker start #启动服务
自定义网桥
docker完成以上网络配置的过程大致是这样的:
1、在主机上创建一对虚拟网卡veth pair设备。veth设备总是成对出现的,它们组成了一个数据的通道,数据从一个设备进入,就会从另一个设备出来。因此,veth设备常用来连接两个网络设备。
2、docker将veth pair设备的一端放在新创建的容器中,并命名为eth0。另一端放在主机中,以veth65f9这样类似的名字命名,并将这个网络设备加入到docker0网桥中,可以通过brctl show命令查看。
3、从docker0子网中分配一个IP给容器使用,并设置docker0的IP地址给容器的默认网关。
docker会尝试寻找没有被主机使用的IP段,尽管它适用于大多数情况下,但是它不是万能的,有时候我们还是需要对ip进一步规划。在启动docker服务的时候,使用-b BRIDGE或—bridge=BRIDGE来指定使用的网桥,需要安装BRIDGE-UTILS软件包。
这里我们新建一个网桥br0作为docker的默认网桥 基本步骤如下:
- 停止服务删除旧网桥
- 创建自定义网桥
- 确认新网桥并启动
- 配置docker默认网桥
停止服务删除旧网桥
# service docker stop
# ip link set dev docker0 down
# brctl delbr docker0
创建新网桥br0
# brctl addbr br0
# ip addr add 192.168.100.1/24 dev br0
查看确认新网桥并启动
# ip addr show br0 或者 brctl show br0
# ip link set dev br0 up
配置docker服务,默认连接到网桥br0上,并启动docker
如果是centos
# echo 'DOCKER_OPTS="-b=br0"' >> /etc/sysconfig/docker
如果是ubuntu
# echo 'DOCKER_OPTS="-b=br0"' >> /etc/default/docker
# service docker start
启动docker服务。新建一个容器,可以看到它已经桥接到了br0上。
可以继续用brctl show命令查看桥接的信息。另外,在容器中可以使用ip addr和ip route命令查看IP地址配置和路由信息。
外部网络访问容器资源
使用-p 本机端口:容器端口 来实现本机端口和容器端口的映射,通过localhost:映射端口 实现容器内应用的访问。
首先需要有一个安装了httpd服务的镜像,或者在未安装httpd服务的容器中安装httpd服务,并将这个容器保存为一个镜像,再依托这个镜像去产生一个新的容器。因为用-p 本机端口:容器端口 去做端口映射,是在容器产生的时候去做的。
此过程省略,下面我们用安装了httpd服务的镜像来做示例,假设镜像为web新建容器名web1(映射本机端口5123到容器端口80)
例:docker run –itd –name web1 –p 5123:80 web bash
测试
可以在docker ps的PORTS列看到这个映射关系 0.0.0.0:5123->80/tcp
容器web1内:`# curl localhost`
宿主机:`# curl localhost:5123`
也可以使用浏览器访问 :`http://ip
容器互联
--link 容器名:别名
比如容器c6mysql是台db容器,依托centos6mysql镜像创建 假设ip为192.168.3.4
c6httpd是台web容器,依托centos6httpd镜像创建
在web容器刚创建的时候可以指定—link c6mysql:db
这样web容器就可以以host和别名的方式与db容器来连接
例:`# docker run --itd --c6httpd --link c6mysql:db centos6httpd bash
这样,在web容器里面ping db和ping c6mysql都是去往3.4这个地址,也就是db容器。
也可以在web容器里面/etc/hosts及env查看到相关信息。
至于这样的方式具体有什么用,目前还不是很清楚。
当时,我们可以针对上面的例子扩展一下:
建立2个容器db和web同时做端口映射来提供服务。
# docker run -itd --name c6mysql -p 13306:3306 centos6mysql bash
# docker run -itd --name c6httpd -p 10080:80 --link c6mysql:db centos6httpd bash
由于头条字数限制暂时只能分析这么多了,对于Docker容器引擎感兴趣的朋友可以关注我相互学习哦
Tags:docker 查看容器状态