一、Docker的简介
什么是Docker
docker是一个开源的应用容器引擎,基于go语言开发并遵循了apache2.0协议开源。docker是一种容器技术,它可以对软件及其依赖进行标准化的打包;容器之间先对独立,基于容器运行的应用之间也是相互隔离的;并且容器之间是共享一个OS kernel的,充分利用服务器资源,容器可以运行在很多主流的操作系统之上。
容器和虚拟机的区别
容器时在linux上运行,并与其它容器共享主机的内核,它运行一个独立的进程,不占用其它任何可执行文件的内存,非常轻量。虚拟机运行的是一个完成的操作系统,通过虚拟机管理程序对主机资源进行虚拟访问,相比之下需要的资源更多。
- 容器是app层面的隔离
- 虚拟机是物理资源层面的隔离
docker架构和底层技术简介
docker本质就是宿主机的一个进程,docker是通过namespace实现资源隔离,通过cgroup实现资源限制,通过写时复制技术(copy-on-write)实现了高效的文件操作(类似虚拟机的磁盘比如分配500g并不是实际占用物理磁盘500g)
底层技术简介
namespace名称空间
namespace的六项隔离
namepace | 系统调用参数 | 隔离内容 |
---|---|---|
UTS | CLONE_NEWUTS | 主机名与域名 |
IPC | CLONE_NEWWIPC | 信号量、消息队列和共享内存 |
PID | CLONE_NEWPID | 进程编号 |
NEWWORD | CLONE_NEWNET | 网络设备、网络栈、端口等 |
MOUNT | CLONE_NEWNS | 挂载点(文件系统) |
USER | CLONE_NEWUSER | 用户和用户组(3.8以后的内核才支持) |
control group控制组
cgroup的功能
- 资源限制:可以对任务使用的资源总额进行限制
- 优先级分配:通过分配的cpu时间片数量以及磁盘IO带宽大小,实际上相当于控制了任务运行优先级
- 资源统计:可以统计系统的资源使用量,如cpu时长,内存用量等
- 任务控制:cgroup可以对任务执行挂起、恢复等操作
二、Docker的环境搭建
CentOS安装docker
- 前往官网下载页
- 卸载docker及相关依赖
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
- 安装yum-utils工具包
sudo yum install -y yum-utils
- 添加docker仓库
sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
- 安装最新版docker引擎(社区版)
sudo yum install docker-ce docker-ce-cli containerd.io
- 安装指定版本的docker引擎
#查询docker版本,版本从高到低排序
yum list docker-ce --showduplicates | sort -r
#安装指定版本的docker,替换即可
sudo yum install docker-ce- docker-ce-cli- containerd.io
- 启动docker
sudo systemctl start docker
- 查看docker版本
sudo docker version
- 检验docker引擎被成功安装
sudo docker run hello-world
三、Docker的镜像、容器和仓库
docker基于linux内核空间,在基础镜像的基础上一层层构建出用户镜像。容器是镜像的运行实例。通过镜像启动一个容器,一个镜像是一个可执行的包,其中包括运行应用程序所需要的所有内容包含代码,运行时间,库、环境变量、和配置文件。
Docker image镜像
Docker镜像是什么
docker镜像就是一个只读模板,比如,一个镜像可以包含一个完整的centos,里面仅安装apache或用户的其他应用,镜像可以用来创建docker容器。
- 文件(root filesystem)和meta data的集合
- image是分层的,并且每一层都可以添加、修改、删除文件,成为一个新的image
- 不同的image可以共享相同的layer
- image本身是read-only只读的
Docker镜像地址
从官方dockerhub拉取镜像非常慢,可以试试国内的镜像源。
Docker添加镜像地址
对于使用 systemd 的系统,请在 /etc/docker/daemon.json 中写入如下内容(如果文件不存在请新建该文件)
{
"registry-mirrors": ["https://hub-mirror.c.163.com","https://reg-mirror.qiniu.com"]
}
#重新加载配置,重启docker server
systemctl daemon-reload
systemctl restart docker
Docker镜像相关命令
- 查看本地已有的镜像列表
docker image ls
docker images
- 删除镜像
docker image rm f6509bac4980
#简写
docker rmi f6509bac4980
- 构建镜像
#根据dockerfile构建一个镜像
docker image build
#从一个被改变的容器创建一个新的镜像
docker container commit
#简写,从一个被改变的容器创建一个新的镜像
docker commit
- 从镜像仓库拉取镜像
#默认从docker hub拉取
docker pull ubuntu:14.04
- 查看指定镜像的创建历史
#通过镜像名称
docker history mysql:5.7
#通过镜像ID
docker history f6509bac4980
- 镜像的发布docker hub官网
#首先登陆,输入dockhub的用户名和密码进行登陆
docker login
#将本地镜像推送到dockerhub,这种方式是不被推荐的,因为镜像是如何构建的是透明的
#正确的做法应该是用dockerhub关联github,通过dockerhub自动拉取保存在github中的
#dockerfile,并自动帮我们构建镜像,这样的镜像就会显得安全可靠。
docker push neojayway/hello-world:latest
Dockerfile语法及实践
dockerfile语法
FROM关键字
#制作base image
FROM scratch
#使用base image
FROM centos
FROM ubuntu:14.04
说明:为了安全起见,尽量使用官方的镜像作为基础镜像。
LABEL关键字
LABEL version="1.0"
LABEL description="this is description"
说明:此关键字的作用是定义镜像的元数据,类似于注释及帮助信息,还是非常必要的
RUN关键字
RUM yum update && yum install -y vim \
python-dev #反斜线换行
说明:RUN关键字用来执行命令并创建新的image layer层,值得注意的是每运行一次RUN,都会生成一层layer,为了避免无用分层,务必合并多条命令成一行,反斜线换行,&&合并成多条命令
shell命令格式
RUN yum install -y vim
CMD echo "hello docker"
ENTRYPOINT echo "hello docker"
exec命令格式
#exec命令格式执行命令,仅仅是单纯的执行命令,没有以shell环境去执行
RUN ["yum", "install", "-y", "vim"]
CMD ["/bin/echo", "hello docker"]
ENTRYPOINT ["/bin/echo", "hello docker"]
#未指定以bash shell环境去执行,$name不会被变量替换
ENTRYPOINT ["/bin/echo", "hello $name"]
#指定以bash shell环境去执行,$name才会被变量替换
ENTRYPOINT ["/bin/bash", "-c", "echo hello $name"]
CMD关键字
FROM centos
ENV name docker
CMD echo "hello $name"
- 将以上dockerfile构建成镜像并执行docker run [image]会输出“hello docker”
- 执行docker run -it [image] /bin/bash就不会输出“hello docker”了
说明:该关键字的作用是设置容器启动后默认执行的命令和参数,如果docker run指定了其它命令,CMD命令将会被忽略,如果定义了多个CMD,只有最后一个会执行
ENTRYPOINT关键字
COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]
EXPOSE 27017
CMD ["mongod"]
说明:设置容器启动时运行的命令,让容器以应用程序或者服务的形式运行,不会被忽略,一定会执行
WORKDIR关键字
#如果不存在此目录,则会自动创建目录,并进入目录
WORKDIR /test
WORKDIT demo
RUN $PWD #输出结果应为/test/demo
说明:WORKDIR设定当前工作目录,进入目录应当使用WORKDIR,避免使用RUN cd,因为RUN会新增层,还有尽量使用绝对目录。
ADD和COPY关键字
ADD test.tar.gz / #添加到根目录并解压缩
COPY hello /
说明:AND and COPY本地文件添加到Docker Image里面,大部分情况,COPY优于ADD,ADD相比COPY有解压缩的功能。如需要添加远程文件或目录,请使用RUN关键字执行curl或者wget命令。
ENV关键字
ENV MYSQL\_VERSION 5.7 #设置常量
RUN apt-get install -y mysql-servier= "${MYSQL_VERSION}" \ && rm -rf /var/lib/apt/lists/* #引用常量
说明:设置常量,增加可维护性。
VOLUME关键字
EXPOSE关键字
Docker container容器
Docker container容器是什么
docker利用容器来运行应用,容器是从镜像创建的运行实例,它可以被启动,开始、停止、删除、每个容器都是互相隔离的,保证安全的平台,可以吧容器看做是要给简易版的linux环境(包括root用户权限、镜像空间、用户空间和网络空间等)和运行再其中的应用程序
- 容器通过image创建(copy)
- 容器在imagelayer之上建立要一个container layer(可读写)
- image负责app的存储和分发,container负责运行app
- 容器与镜像的关系类似于类和实例的关系
Docker容器相关命令
- 查看容器列表
#当前正在运行的容器列表
docker container ls
#列出所有容器,包含已退出的
docker container ls -a
docker ps -a
- 运行一个容器
docker run centos
#指定容器名字,以后台进程方式运行
docker run -d --name=demo neojayway/hello-world
#以交互模式运行一个容器
docker run -it centos
- 限制容器资源
#限制容器内存为200M
docker run --memory=200M neojayway/hello-world
#设置cpu相对权重
docker run --cpu-shares=10 neojayway/hello-world
- 删除容器
#指定容器ID删除
docker container rm d02f80816fbb
#简写
docker rm d02f80816fbb
#删除所有的容器,-q选项只列出容器ID
docker rm $(docker container ls -aq)
#删除退出状态的容器,-q选项只列出容器ID
docker rm $(docker container ls -f "status=exited" -q)
- 进入容器
#以交互方式进入指定的容器中
docker exec -it 11a767d3a588 /bin/bash
#查看指定容器的ip地址
docker exec -it 11a767d3a588 ip a
- 停止容器
docker container stop 11a767d3a588
#简写
docker stop 11a767d3a588
- 启动容器
docker container start 11a767d3a588
#简写
docker start 11a767d3a588
#指定名称启动容器
docker start demo
- 检查容器
#简写,查看容器的详细信息
docker inspect 11a767d3a588
#指定名称检查容器
docker inspect demo
- 查看容器日志
#简写,查看容器运行输出的一些日志
docker logs 11a767d3a588
Docker repository仓库
Docker repository仓库是什么
仓库是集中存储镜像文件的地方,registry是仓库注册服务,实际上仓库注册服务器上存放着多个仓库,每个仓库中又包含了多个镜像,每个镜像有不同的标签(tag)。
仓库分为两种,即公有仓库和私有仓库,最大的公开仓库是docker Hub,存放了数量庞大的镜像供用户下载,国内的docker pool,这里仓库的概念与Git类似,registry可以理解为github这样的托管服务
四、Docker的网络
Linux网络命名空间
网络命名空间(network namespace)为命名空间内的所有进程提供了全新隔离的网络协议栈。这包括网络接口,路由表和iptables规则。通过使用网络命名空间就可以实现网络虚拟环境,实现彼此之间的网络隔离,Docker中的网络隔离也是基于此实现的。
利用Linux网络命名空间模拟docker网络通信
运行docker容器并观察其网络信息
#分别运行以下两个容器,busybox是非常小的linux image,
docker run -d --name test1 busybox /bin/sh -c "while true; do sleep 3600; done"
docker run -d --name test2 busybox /bin/sh -c "while true; do sleep 3600; done"
#查看容器的网络命名空间
docker exec aa809330a169 ip a
#进入容器
docker exec -it aa809330a169 /bin/sh
#验证容器之间的网络是否也是可达的
docker exec aa809330a169 ping 172.17.0.2
说明:宿主机上docker容器之间的网络是互通,借助于linux网络命名空间,每个docker容器有着不同的命名空间。
Linux网络命名空间模拟docker网络通信
实验通过创建两个两个linux网络命名空间,以及一对虚拟网卡接口对(veth pair),然后分别将虚拟接口分别附加到网络命名空间中,并为虚拟接口设置ip并启用,然后测试两个命名空间的网络可达性。
手动创建linux网络命名空间
#查看本机的网络命名空间列表
ip netns list
#新建网络命名空间
ip netns add test1
ip netns add test2
#删除网络命名空间
ip netns delete test1
查看手动创建linux网络命名空间
查看网络命名空间信息
ip netns exec test1 ip a
#查看网络命名空间中的连接
ip netns exec test1 ip link
#将本地回环连接状态启用,所谓连接需要连接两个网卡端点,只有一端是无法up的
ip netns exec test1 ip link set dev lo up
说明:此新建的网络命名空间中仅有一个本地回环接口,没有地址并且状态还是down的。
创建虚拟网卡对(veth pair)
#创建 veth-test1 和 veth-test2 一对虚拟网卡接口
ip link add veth-test1 type veth peer name veth-test2
说明:此时宿主机上多了两个虚拟网卡接口,就是我们上面创建的一条链接(veth pair),我们需要将这两个虚拟网卡接口分别添加到上面创建的test1、test2命名空间中。
为网络命名空间添加虚拟网卡接口
#分别将这一对虚拟网卡接口添加给对应的网络命名空间
ip link set veth-test1 netns test1
ip link set veth-test2 netns test2
说明:此时宿主机上的两个虚拟网卡接口小时不见,test1、test2命名空间中分别被添加了虚拟网卡接口veth-test1、veth-test2,但是此时这两个虚拟接口仍然没有分配地址等资源。
为网络命名空间中虚拟网卡接口添加ip地址等资源
#分别为网络命名空间下的虚拟接口附加ip地址和子网掩码资源
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
启动虚拟网卡接口
ip netns exec test1 ip link set dev veth-test1 up
ip netns exec test2 ip link set dev veth-test2 up
测试网络命名空间虚拟网卡接口可达性
ip netns exec test1 ping 192.168.1.2
docker桥网络
如下图所示,docker容器之间并不是直接连通的,而是通过中间的桥接点docker0做中转,docker0是宿主机默认网络命名空间里的虚拟接口,各个容器与桥节点docker0之间的连接通过虚拟网卡接口对实现,实现了容器间互通;然后docker0通过NAT(网路地址转换)与宿主机的eth0虚拟接口连通,则容器可以借助宿主机访问外网的能力。
docker桥网络相关详情查看命令
#安装桥网络相关工具包
yum install -y bridge-utils
#查看桥网络列表,如哪些虚拟接口已连接到桥网络上了
brctl show
#查看docker桥网络详情,如哪些容器连接到了桥网络上
docker network inspect bridge
docker容器网络之host和none
上面介绍了bridge网络,接下来介绍host和none网络。对于none网络,使用none网络的容器是无法被外界访问的,容器内部的网络命名空间是被孤立的,也只有一个本地回环端点,只能在宿主机本地进入容器内部的方式;
- docker容器网络之none
#运行容器,采用none网络
docker run -d --name none-nw --network none centos /bin/sh -c "while true; do sleep 3600; done"
- docker容器网络之host
#运行容器,采用host网络
docker run -d --name host-nw --network host centos /bin/sh -c "while true; do sleep 3600; done"
说明:采用host网络运行容器,容器同样不会单独分配ip,因为它完全是用宿主机的网络命名空间。
docker容器之间的link
试想一下,一台宿主机上有两个容器,一个容器中跑了一个mysql服务,另外一个容器中跑了一个web服务,web服务需要访问mysql服务,而两个容器的ip又是动态的,那么如何能使web服务能够通过指定容器名称就能访问到mysql服务,容器之间的link就可以做到。
docker run -d --name test1 busybox /bin/sh -c "while true; do sleep 3600; done"
#test2容器连接test1,是有方向的
docker run -d --name test2 --link test1 centos /bin/sh -c "while true; do sleep 3600; done"
说明:test2容器与test1容器之间link了,test2容器可以直接以test1容器名称测试容器直接的可达性。
docker容器之间的采用新建的bridge连接
默认情况下docker容器之间是通过docker0这个默认的bridge连接的,我们也可以新建一个bridge,然后指定容器使用这个我们自定义的bridge,与默认的bridge的区别在于,指定使用自定义的bridge的容器会默认相互link(添加DNS记录),这样容器之间可以通过容器名称通信了。
新建bridge
#创建bridge,-d指定driver
docker network create -d bridge my-bridge
#查看docker的网络名称空间
docker network ls
#查看桥网络
brctl show
容器采用自定义的bridge网络
#--network指定bridge网络,不指定则使用默认docker0
docker run -d --name test2 --network my-bridge centos /bin/sh -c "while true; do sleep 3600; done"
#将已经存在的容器添加到自定义容器上了,此时test1容器同时连接到多个桥网络上了
docker network connect my-bridge test1
docker容器的端口映射
docker容器中服务的端口需要在宿主机中一个端口进行绑定映射,这样才能被外部访问到。
#启动一个Nginx服务,将容器的80端口与宿主机的80端口映射
docker run -d -p 80:80 --name web nginx
五、Docker部署简单实验
单机多容器部署
在单台虚拟机上运行两个容器,一个容器运行redis服务,一个容器运行Python web小程序,并且web小程序需要访问到另一个容器中的redis服务。
- 创建容器运行redis服务
#运行一个redis服务,同宿主机不同容器之间访问,无需映射端口
docker run -d --name redis redis
- 编写Python web小程序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)
说明:此web小程序暴露一个http端点,访问一次此端点则借助于redis服务记录一次访问量。
- 新建Dockerfile
FROM python:2.7
LABEL maintaner="neojayway [email protected]"
COPY . /app
WORKDIR /app
RUN pip install flask redis
EXPOSE 5000
CMD [ "python", "app.py" ]
- 构建flask-redis镜像,请运行容器
#通过Dockerfile构建镜像
docker build -t flask-redis /home/docker_practice/demo1/
#运行容器,-e通过设置环境变量,使得容器内部可以读取,直接通过redis服务容器的名称访问redis
docker run -d -p 5000:5000 --link redis --name flask-redis -e REDIS_HOST=redis flask-redis
- 验证
curl 127.0.0.1:5000