2018-12-20    2018-12-20    8083 字  17 分钟

一、简介

​ Docker是PASS【Platform As A Service】提供商dotCloud开源的一个基于LXCLinux Container】的高级容器引擎,源代码托管在Github上。基于go语言并遵从Apache2.0协议开源

​ Docker的主要目标是“Build and Run Any App, Anywhere“,也就是通过对应用组件的封装、分发、部署、运行等生命周期的管理,使用户的APP及其运行环境能够做到**“一次封装,到处运行”**。

传统的开发部署协作方式

传统部署

Docker的开发部署协作方式

docker部署

二、虚拟化技术

虚拟机技术

虚拟机

​ 虚拟机 (VM) 是一个物理硬件层抽象,用于将一台服务器变成多台服务器。管理程序允许多个 VM 在一台机器上运行。每个 VM 都包含一整套操作系统、一个或多个应用、必要的二进制文件和库资源,因此占用大量空间。而且 VM 启动也十分缓慢。

容器虚拟化技术

容器

​ 容器是一个应用层抽象,用于将代码和依赖资源打包在一起。多个容器可以在同一台机器上运行,共享操作系统内核,但各自作为独立的进程在用户空间中运行。与虚拟机相比,容器占用的空间较少(容器镜像大小通常只有几十兆),瞬间就能完成启动。

三、Docker三要素

镜像(Image)

​ 镜像就是一个只读模板。镜像可以用来创建Docker容器,一个镜像可以创建多个容器。

容器(Container)

​ Docker利用容器独立运行一个或一组应用。容器是镜像创建的运行示例。它可以被启动、停止、删除。每个容器都是相互隔离的、保证安全的平台。

仓库(Repository)

​ 仓库是集中存放镜像文件的场所。仓库和仓库注册服务器(Registry)是有区别的。仓库注册服务器上往往存放着多个仓库,每个仓库又包含了多个镜像,每个镜像又不同的标签(tag)。

注:Docker 本身是一个容器运行载体或称之为管理引擎。我们把应用程序和配置依赖打包形成一个可交付的运行环境,这个打包好的运行环境就是镜像文件。只有通过这个镜像文件才能生成Docker容器。镜像文件可以看作是容器的模板。Docker根据镜像文件生成容器的实例。同一个镜像文件,可以生成多个同时运行的容器实例。

四、YUM安装Docker

第一步:卸载原Docker组件,确认安装了gcc、gcc-c++

1
2
3
4
5
6
7
#卸载旧docker组件
yum remove docker docker-client docker-client-latest docker-common docker-latest \
docker-latest-logrotate docker-logrotate docker-selinux \
docker-engine-selinux docker-engine

#安装依赖
yum -y install gcc gcc-c++

第二步:安装Docker仓库

1
2
3
4
5
#安装必要的一些系统工具
yum install -y yum-utils device-mapper-persistent-data lvm2

#添加软件源信息,使用阿里云下载
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

第三步:更新、安装、开启Docker服务

1
2
3
4
5
6
#更新并安装Docker-CE
yum makecache fast
yum -y install docker-ce

#开启Docker服务
systemctl start docker

第四步:配置镜像加速器

1
2
3
4
5
6
7
8
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["自己的阿里云加速器地址"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

第五步:运行hello-world镜像验证是否安装成功

1
docker run hello-world

五、命令

帮助命令

  • 查看版本信息 docker version
  • 查看详细信息 docker info
  • 查看命令帮助 docker –help

镜像命令

  • 列出镜像 docker images

    参数描述
    -a列出本地主机上的镜像(包括中间层镜像)
    -q列出本地主机上的镜像ID
  • 搜索镜像

    docker search [镜像名]

    参数描述
    –filter=stars=3列出点赞数不小于指定值的镜像
    –no-trunc显示完整镜像描述
  • 拉取/删除镜像

    docker pull/rmi [镜像名]:[Tag]

    小技巧:docker rmi -f $(docker images -qa) 删除所有镜像

  • 提交容器作为镜像

    docker -a=“作者名” -m=“描述信息” commit [容器ID] 名称:Tag标签

  • 保存【导出】镜像

    docker save [镜像名]:[Tag] -o /path/[name].tar

  • 加载镜像

    docker load -i [name].tar

容器命令

  • 运行容器命令 docker run [镜像名]:[Tag]

    参数描述
    -i以交互模式运行容器,通常与-t同时使用
    -t为容器重新分配一个伪输入终端,通常与-i同时使用
    -p指定映射端口,形式hostPort:ContainerPort
    -P随机端口映射
    -d后台运行容器
    –name为容器指定名称
    –restart=alwaysdocker服务启动容器就启动
    -h name设置主机名
    –add-host name:ip注入hostname的IP解析
    –rm容器停止时自动删除
    –link cname:hostName向容器的/etc/hosts添加另一个IP地址的映射形式
  • 退出容器方法

    • exit 容器停止退出
    • ctrl+p+q 容器不停止退出
  • 查看容器进程 docker ps

    参数描述
    -l上次运行的容器
    -a所有运行过的容器
    -q静默只显示容器编号
  • 启动/重启/停止容器

    docker start/restart/stop [容器ID]

  • 强制停止容器

    docker kill [容器ID]

  • 删除关闭的容器

    docker rm [容器ID]

    参数描述
    -f强制删除

    小技巧:docker rm -f $(docker ps -qa) 删除所有容器

  • 查看容器日志 docker logs [容器ID]

    参数描述
    -f一直追加,显示最新日志
    -t显示每条日志时间戳
  • 查看容器运行进程信息

    docker top [容器ID]

  • 查看容器内部细节

    docker inspect [容器ID]

  • 查看容器占用的系统资源

    docker stats [容器ID]

    不加容器ID则查看所有容器的系统资源

  • 重新进入容器

    docker attach [容器ID]

  • 进入容器执行Shell命令

    docker exec -it [容器id Shell命令]

  • 拷贝容器内文件

    docker cp [容器ID]:容器路径 本机路径

  • 容器运行机制

​ Docker容器后台运行,就必须有一个前台进程,容器运行的命令如果不是会挂起的命令(比如top),会自动退出。最佳的解决方案是,将要运行的程序以前台进程的形式运行

六、Docker Compose

​ Docker Compose是容器编排工具,允许用户在一个模板(YAML格式)中定义一组相关联的容器,对启动的优先级进行排序。

命令解释
-f指定YAML文件位置
ps显示容器所有信息
start/stop/restart启动/停止/重启容器【已加载成为容器】
logs查看日志信息
config -q验证YAML是否正确
up -d启动容器项目【未加载成为容器】
pause暂停容器
unpause恢复暂停
rm删除容器

简单示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
version: '3'
services: 
  db: 
    image: 'mysql:5.7'
    restart: 'always'
    environment: 
      MYSQL_ROOT_PASSWORD: 'root'
      MYSQL_DATABASE: 'wordpress'
      MYSQL_USER: 'wordpress'
      MYSQL_PASSWORD: 'wordpress'
  wordpress: 
    depends_on:
      - db
    image: 'wordpress:latest'
    restart: 'always'
    ports: 
      - '8080:80'
    environment: 
      WORDPRESS_DB_HOST: 'db:3306'
      WORDPRESS_DB_USER: 'wordpress'
      WORDPRESS_DB_PASSWORD: 'wordpress'

七、镜像加载原理

UnionFS 联合文件系统

​ 联合文件系统是一种分层、轻量级并且高性能的文件系统,他支持对文件系统的修改作为一次提交来一层一层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下。Union文件系统是Docker镜像的基础。镜像可以通过分成来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。

​ 特征:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。

Docker镜像加载原理

​ docker的镜像实际上由一层一层的文件系统组成即联合文件系统。

docker镜像原理

​ bootfs(boot file system)主要包含bootloader和kernel, bootloader 主要是引导加载kernel, Linux刚启动时会加载bootfs文件系统,在 Docker镜像的最底层是bootfs.这一层与我们典型的Linux/Unix系统是一一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权己由bootfs转交给内核,此时系统也会卸载bootfs。

​ rootfs (root file system),在bootfs之 上.包含的就是典型Linux系统中的/dev, /proc, /bin, /etc等标准目录和文件。rootfs就是 各种不同的操作系统发行版,比如Ubuntu, Centos等等 。

​ 对于一个精简的OS,rootfs 可以很小,只需要包括最基本的命令、工具和程序库就可以了,因为底层直接用宿主机的kernel,自己只需要提供rootfs就行了。由此可见对于不同的linux发行版,bootfs基本是一致的,rootfs会有差别,因此不同的发行版可以公用bootfs 。

docker镜像结构

​ 注意:docker在bootfs自检完毕之后并不会把rootfs的read-only改为read-write。而是利用union mount(UnionFS的一种挂载机制)将一个或多个read-only的rootfs加载到之前的read-only的rootfs层之上。在加载了这么多层的rootfs之后,仍然让它看起来只像是一个文件系统,在Docker的体系里把union mount的这些read-only的rootfs叫做Docker的镜像。但是,此时的每一层rootfs都是read-only的,我们此时还不能对其进行操作。当我们创建一个容器,也就是将Docker镜像进行实例化,系统会在一层或是多层read-only的rootfs之上分配一层空的read-write的rootfs。

八、容器数据卷

简介

​ 容器数据卷的作用是数据共享和数据持久化。卷就是目录或文件,存在于一个或多个容器中,由docker挂载到容器,但不属于联合文件系统,因此能够绕过联合文件系统,提供一些用于持续存储或共享数据的功能。

​ 卷的设计目的就是持久化,完全独立于容器的生命周期,因此docker不会在容器删除时删除其挂载的数据卷。如果没有指定映射卷位置(容器数据卷)将会在/var/lib/docker/volumes下创建默认容器管理数据卷

容器卷具有以下特点:

  • 容器卷可在容器之间共享或重用数据
  • 卷中的更改可以直接生效
  • 数据卷中的更改不会包含在镜像的更新当中
  • 数据卷的生命周期一直持续到没有容器使用它为止

使用容器数据卷

挂载完成可以使用docker inspect命令查看相关数据卷挂载信息

  • 使用命令

    docker run [options] -v [宿主机绝对路径]:[docker容器绝对路径] 镜像ID 挂载容器数据卷

    docker run [options] -v [宿主机绝对路径]:[docker容器绝对路径]:[ro] 镜像ID 只读模式挂载

    指定宿主机路径,目的时为了读写数据

    若不指定宿主机路径,则docker自动创建一个目录,这样使用是为了共享数据

    注意:当宿主机删除与容器挂载的目录时,容器的挂载目录变为只读。即使宿主机恢复容器的挂载目录也无效

  • 使用DockerFile文件的VOLUME命令创建容器管理数据卷

    VOLUME [“docker容器绝对路径”,“docker容器绝对路径”]

    只会在容器内创建,宿主机目录由docker自行创建,目的时为了数据共享

  • 数据卷容器

    命名的容器挂载数据卷,其它容器通过挂在这个父容器实现数据共享,挂载的容器被称为数据卷容器

    docker run [options] –volumes-from 数据卷容器名称 镜像名称

九、DockerFile

基础知识

  • 每条保留字指令都必须为大写字母且后面至少跟随一个参数
  • 指令按照从上到下,顺序执行
  • #表示注释
  • 每条指令都会创建一个新的镜像层,并对镜像进行提交
  • 最大不能超过128层

保留关键字

关键字描述
FROM基础镜像,当前新镜像时基于哪个镜像
LABEL镜像维护者的信息
RUN执行的SHELL命令
EXPOSE暴露服务的端口,只起显示作用
WORKDIR终端登陆之后的工作目录
ENV设置环境变量
ADD把文件拷贝到镜像并解压【必须和Dockerfile在同一级目录下,不用加绝对路径】
COPY把文件拷贝到镜像【必须和Dockerfile在同一级目录下,不用加绝对路径】
VOLUME创建数据容器卷
CMD指定容器启动时要运行的命令,可以有多个CMD命令,但只有最后一个生效,CMD会被docker run 之后的参数替换
ENTRYPOINT指定容器启动时要运行的命令,不会覆盖只会追加
ONBUILD当构建一个被继承的Dockerfile时运行的命令,父镜像在被子镜像继承时触发

示例

  • 简单的使用
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#在centos上架构
FROM centos
#作者信息
LABEL org.kun.vendor="WangYuKun" org.kun.build-date="20181218"
#设置环境变量,会存在于容器环境变量当中
ENV USERLOCAL /usr/local
#安装vim
RUN yum install -y vim
#设置登陆终端时的交互目录
WORKDIR ${USERLOCAL}
#启动执行的命令
CMD ["/bin/bash"]
  • CMD和ENTRYPOINT
1
2
3
4
5
6
7
8
9
#在centos上架构
FROM centos
#安装curl
RUN yum install -y curl
#执行时可以在后追加参数,例如docker run [镜像名] -i 相当于 curl -s https://ip.cn
ENTRYPOINT ["curl","-s","https://ip.cn"]

#执行不可以追加参数相当于覆盖操作
#CMD ["curl","-s","https://ip.cn"]
  • ONBUILD
1
2
3
4
FROM centos
RUN yum install -y curl
#在子类构建镜像时会触发,注意ONBUILD仅仅时触发器,后面需要跟保留关键字起作用,不能直接使用命令
ONBUILD RUN ["echo","onbuild tigger"]
  • 自定义tomcat
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
FROM centos
#拷贝jdk压缩包并解压
ADD jdk-8u191-linux-x64.tar.gz /opt/
RUN mv /opt/jdk1.8.0_191 /opt/jdk8
#配置java环境变量
ENV PATH=$PATH:/opt/jdk8/bin
ENV JAVA_HOME=/opt/jdk8

#拷贝tomcat压缩包并解压
ADD apache-tomcat-8.5.35.tar.gz /opt/
RUN mv /opt/apache-tomcat-8.5.35 /opt/tomcat
ENV CATALINA_HOME=/opt/tomcat
#设置工作目录
WORKDIR $CATALINA_HOME
#设置要暴露的端口
EXPOSE 8080

#启动tomcat并打印日志
CMD /opt/tomcat/bin/startup.sh start && tail -f /opt/tomcat/logs/catalina.out

十、镜像仓库搭建

官方仓库搭建方式

仓库端

  • 启动仓库
1
docker run -d -v /data/registry:/var/lib/registry -p 5000:5000 --restart=always registry
  • 修改【 /etc/docker/deamon.json】文件
1
2
3
{
  "insecure-registries":["仓库端IP:5000"]
}
  • 重启docker服务
1
systemctl restart docker

客户端

  • 修改【 /etc/docker/deamon.json】文件
1
2
3
{
  "insecure-registries":["仓库端IP:5000"]
}
  • 重启docker服务
1
systemctl restart docker
  • 查看仓库镜像
1
2
3
4
# 查看镜像
curl -XGET http://registrIP:5000/v2/_catalog
# 查看具体镜像版本
curl -XGET http://registrIP:5000/v2/image_name/tags/list
  • 打包上传镜像
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 命令格式docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[/libraryName][:TAG]
# 指定仓库需要加libraryName,官方搭建方式不能指定仓库,harbor可以创建仓库
# 例如:docker tag hello-world:latest 192.169.1.149:5000/hello-world:v1.0
docker tag SOURCE_IMAGE:[TAG] registrIP:5000[/userName]/TARGET_IMAGE:[TAG] 

# 上传镜像到仓库
# 私有仓库登陆才能推送
# docker login ip/hostname
# 例如:docker push 192.168.1.149:5000/hello-world:v1.0
docker push registrIP:5000[/userName]/TARGET_IMAGE:[TAG]

Harbor仓库搭建

Harbor构建需要先安装python、compose。harborGithub地址

  • 构建SSL证书
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 构建证书
openssl genrsa -des3 -out server.key 2048
openssl req -new -key server.key -out server.csr

# 脱密,证书退密钥
cp server.key server.key.org
openssl rsa -in server.key.org -out server.key

# 证书有效期
openssl x509 -req -days 365 -in server.csr -signkey server.key -out  server.csr

# 将证书移置指定目录
mkdir /data/cert
chown -R 777 /data/cert
cp server.*  /data/cert
  • 修改harbor.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 修改hostname项
hostname: [hostname]

# 屏蔽HTTP协议
#http:
#  port: 80

# 开放https协议
https:
  port: 443
  certificate: /data/cert/server.csr
  private_key: /data/cert/server.key
  • 执行安装脚本
1
./install.sh
  • 访问harborhttps://ip/harbor/projects,默认账户:admin,密码:Harbor12345

客户端配置(非进行公网认证过的SSL证书)

  • 修改【 /etc/docker/deamon.json】文件
1
2
3
{ 
 "insecure-registries":["registryIP"]
}
  • 重启docker服务
1
systemctl restart docker

打包示例

docker tag hello-world:latest www.kun.com/library/hello-world:v1.0

推送示例

先登录:docker login www.kun.com

docker push www.kun.com/library/hello-world:v1.0

十一、网络设置

网络通讯

Docker-net
  • 容器与容器之间通讯使用docker的网桥(类似交换机作用)进行相互之间铜须

  • 容器访问外部网络使用SNAT转换

  • 外部网络访问容器使用DNAT转换

网络模式修改

进程网络修改【不常用】

  • -b,–bridge=”“:指定Docker使用的网桥设备,默认情况下Docker会自动创建和使用docker0网桥设备,通过此参数可以设置已经存在的设备

  • –bip:指定Docker0的IP和掩码,使用标准的CIDR格式如10.10.10.10/24

  • –dns:配置容器的DNS,在启动Docker进程时添加,所有容器全部生效

容器网络修改

  • –dns:配置容器的DNS
  • –net:指定容器的网络通讯方式
    • bridge:Docker默认方式,网桥模式
    • none:容器没有网络栈
    • container:使用其它容器的网络栈,容器会加入其它容器的network namespace
    • host:表示容器使用Host的网络,没有自己独立的网络栈,容器可以完全访问Host的网络,不安全

端口暴露方式

p/P 选项的使用格式

  • -p : 将制定的容器端口映射至主机所有地址的一个动态端口
  • -p : 映射至指定的主机端口
  • -p :: 映射至指定的主机的IP的动态端口
  • -p :: 映射至指定的主机IP的主机端口
  • -P(大写) 暴露所需要的所有端口(expose端口)到随机端口

docker port [ontainerName] 可以查看容器当前的映射关系

容器间网络隔离

第一步:创建独立network namespace

1
2
3
4
docker network create -d bridge \
[--subnet "172.26.10.0/16"] \
[--gateway "172.26.10.1"] \
[netName]

第二步:运行镜像是指定network

1
docker run -d --network=[netName] [imageName]

查看namespace: docker network ls

十二、内存&CPU限制

​ 默认情况下Docker创建的容器会尽可能是用完操作系统内存,当服务异常时可能会导致崩溃,所以生产环境下CPU和内存必须加以限制。Dockers底层使用CGroup进行资源限制。

Linux内核有OOME机制【Out Of Memory Exception】

​ 一旦发生OOME,任何进程都有可能被杀死,包括docker daemon在内,为此docker调整了docker daemon的OOM优先级,以免被内核关闭。

内存相关设置

  • -m,–memory 容器能使用的最大内存大小,单位M

  • –memory-swap 容器可使用的swap大小

    • –memory为正数M,–memory-swap为正数S,swap空间为(S-M)
    • –memory为正数M,–memory-swap为0或者unset,容器可用swap为2*M
    • –memory为正数M,–memory-swap为-1,使用主机的swap限制

    容器内使用free看到的swap空间不具有真实含义

  • –memory-swappiness swap出内存的比例。取值0-100,0代表不使用swap

  • –memory-resercation 内存软限制

  • –oom-kill-disable 发生OOME时是否杀死容器,只有配置-m时使用才安全,否则会造成内存耗尽

CPU相关设置

​ Docker提供的CPU资源限制选项可以在多核系统上限制容器能利用哪些vCPU,而对容器最多能使用的CPU时间 有两种限制方式:①设置各个容器能使用的CPU时间相对比例。②以绝对的方式设置容器在每个调度周期内最多能使用的时间

  • –cpuset-cpus="" 使用的CPU集,例如:“0,1,1-3"分别表示使用第一块、第二和第一、第二、第三块CPU
  • -c,–cpu-shares=1024 CPU使用权重,默认1024
  • –cpu-period=0 一个调度周期时间单位微秒(1000~1000000),和cpu-quota联用
  • –cpu-quota=0 一个调度周期使用的CPU时间单位微妙,和cpu-period联用

docker run -it –cpu-period=10000 –cpu-quota=20000 centos /bin/bash #代表使用两块CPU

十三、Docker安装MySQL、Redis

  • 安装MySQL
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#拉取MySQL
docker pull mysql:5.7

#注意外部访问需要修改root权限,docker mysql配置文件再/etc/mysql目录下,注意查看内容
docker run -p3306:3306 \
-v /root/mysql/log/mysqld.log:/logs \
-v /root/mysql/data:/var/lib/mysql \
-v /root/mysql/conf/my.cnf:/etc/mysql/my.conf \
-e MYSQL_ROOT_PASSWORD=123456  \
-d mysql:5.7
  • 安装Redis
1
2
3
4
5
6
7
8
9
#拉取redis
docker pull redis

#启动redis,注意redis.conf中的dir要有权限,bind注释掉,protected-mode改为no
docker run -p6379:6379 \
-v /root/redis/conf/redis.conf:/usr/local/etc/redis.conf \
-v /root/redis/data:/data \
--privileged=true 
-it redis redis-server /usr/local/etc/redis.conf

附名词解释&容器状态转换图

IASS&PASS&SASS

传统部署

LXC

​ Linux的container技术在内核层面由两个独立的机制保证,一个保证资源的隔离性,名为namespace,一个进行资源的控制,名为cgroup。

namespace

​ namespace是对全局系统资源的一种封装隔离,使得处于不同namespace的进程拥有独立的全局系统资源,改变一个namespace中的系统资源只会影响当前namespace里的进程,对其他namespace中的进程没有影响。

​ Linux现有的namespace有6种: uts[主机名], pid, ipc[进程通信], mnt[挂载点], net和user

cgroup

​ cgroup是Linux下的一种将进程按组进行管理的机制,在用户层看来,cgroup技术就是把系统中的所有进程组织成一颗一颗独立的树,每棵树都包含系统的所有进程,树的每个节点是一个进程组,而每颗树又和一个或者多个subsystem关联,树的作用是将进程分组,而subsystem的作用就是对这些组进行操作。

subsystem

​ 一个subsystem就是一个内核模块,他被关联到一颗cgroup树之后,就会在树的每个节点(进程组)上做具体的操作。subsystem经常被称作"resource controller”,因为它主要被用来调度或者限制每个进程组的资源【比如限制CPU的使用时间,限制使用的内存,统计CPU的使用情况,冻结和恢复一组进程等】

hierarchy

​ 一个hierarchy可以理解为一棵cgroup树,树的每个节点就是一个进程组,每棵树都会与零到多个subsystem关联。在一颗树里面,会包含Linux系统中的所有进程,但每个进程只能属于一个节点(进程组)。系统中可以有很多颗cgroup树,每棵树都和不同的subsystem关联,一个进程可以属于多颗树,即一个进程可以属于多个进程组,只是这些进程组和不同的subsystem关联。

容器状态转换图

container-status-convert