1. Docker的Namespace资源隔离

Namespace 技术实际上修改了应用进程看待整个计算机的“视图”,即它的“视线”被操作系统内核做了限制,只能看到指定的内容。

6大隔离:

  • MNT Namespace(Mount): 提供磁盘挂载点和文件系统的隔离能力。系统调用CLONE_NEWNS,Linux 2.4.19内核开始支持。宿主机使用Chroot技术把容器锁定到一个指定的运行目录中,如/var/lib/containerd/io.containerd.runtime.v1.linux/moby/容器ID。
  • IPC Namespace:提供进程间通信的隔离能力。系统调用CLONE_NEWIPC, Linux 2.6.19 内核开始支持。即一个容器内的进程间通信,允许一个容器内的不同进程(内存、缓存等)数据访问,但是不能夸容器访问其他容器的数据。
  • UTS Namespace:提供主机名隔离能力。系统调用CLONE_NEWUTS,Linux 2.6.19 内核开始支持。UTS(UNIX Timesharing System包含了运行内核的名称、版本、底层体系结构类型等信息)用于系统标识,其中包含了hostname和域名domainname,使得一个容器拥有属于自己的hostname标识,这个主机名标识独立于宿主机系统和其它容器。
  • PID Namespace:提供进程隔离能力。系统调用CLONE_NEWPID,Linux 2.6.24 内核开始支持。在Linux系统中,有一个PID为1的进程(init/systemd)是其它所有进程的父进程(上帝进程),所以在容器中也要有一个父进程来管理下属子进程,即通过PID Namespace实现进程PID的隔离(比如PID编号重复,容器内主进程生成与回收子进程等)。如在docker中采用Containerd作为运行时,则docker调用Contained容器运行时的API接口,通过Contained-shim作为一个真实容器的载体运行容器(即一个容器对应一个Containerd-shim),而在容器中一个真实的应用进程作为上帝进程(如下图的Nginx容器中的Nginx进程)。

docker之间网络隔离 docker的六种隔离_nginx

docker之间网络隔离 docker的六种隔离_docker之间网络隔离_02

docker之间网络隔离 docker的六种隔离_docker_03

  • Net Namespace:提供网络隔离能力。系统调用CLONE_NEWNET,Linux 2.6.29 内核开始支持。允许容器拥有独立的网卡、监听的端口、TCP/IP协议栈等。一个Linux虚拟网桥形式的docker网络如下图:

docker之间网络隔离 docker的六种隔离_ubuntu_04

  • User Namespace:提供用户隔离能力。系统调用CLONE_NEWUSER,Linux 3.8 内核开始支持。允许在宿主机的各个容器内创建相同的用户名以及相同的用户UID和GID,会把其限定在每个容器内。

2. Docker的Cgroup资源限制

Linux Control Groups: 实现对一个进程组的资源的限制,包括CPU、内存、磁盘、网络带宽等,并能够进行优先级设置,以及实现对的进程的挂起和恢复等操作。

docker之间网络隔离 docker的六种隔离_nginx_05

  • blkio: 块设备的IO限制
  • cpu:使用调度程序为cgroup任务提供cpu访问
  • cpuacct:产生cgroup任务的cpu资源报告
  • cpuset:多核心cpu条件下,该子系统会为cgoup任务分配单独的cpu和内存(cpu绑定)
  • devices:运行或拒绝cgroup任务对设备的访问
  • freezer:暂停或者恢复cgroup任务
  • memory:设置每个cgroup的内存限制以及产生的内存使用报告
  • net_cls:标记每个网络包以供group方便使用
  • ns:命名空间子系统
  • perf_event:增加来对每个group的监测跟踪能力,可以监测属于某个特定的group 的所有线程以及运行在特定cpu上的线程。

docker之间网络隔离 docker的六种隔离_docker之间网络隔离_06

如对docker某一个容器进行cpu的使用进行限制:

#未限制,默认对宿主机的cpu使用不做限制
root@ubuntu:~# cat /sys/fs/cgroup/cpu/docker/b80df22af1dfdddf1e353e94316962aa801dedf201a847a7a6b29d311cca20c7/
cgroup.clone_children      cpuacct.usage_percpu_sys   cpu.shares
cgroup.procs               cpuacct.usage_percpu_user  cpu.stat
cpuacct.stat               cpuacct.usage_sys          notify_on_release
cpuacct.usage              cpuacct.usage_user         tasks
cpuacct.usage_all          cpu.cfs_period_us
cpuacct.usage_percpu       cpu.cfs_quota_us
root@ubuntu:~# cat /sys/fs/cgroup/cpu/docker/b80df22af1dfdddf1e353e94316962aa801dedf201a847a7a6b29d311cca20c7/cpu.cfs_period_us
100000
root@ubuntu:~# cat /sys/fs/cgroup/cpu/docker/b80df22af1dfdddf1e353e94316962aa801dedf201a847a7a6b29d311cca20c7/cpu.cfs_quota_us
-1

#cpu.cfs_period_us:cpu分配的周期(微秒),默认为100000。
#cpu.cfs_quota_us:表示该control group限制占用的时间(微秒),默认为-1,表示不限制。如果设为50000,表示占用50000/10000=50%的CPU。


#测试对容器使用的cpu进行限制,测试宿主机为2C4G。
root@ubuntu:~# docker pull lorel/docker-stress-ng   #下载docker压测镜像

root@ubuntu:~# docker run -it --rm --name cpu_unlimit lorel/docker-stress-ng --cpu 2 --vm 2

root@ubuntu:~# docker stats 
CONTAINER ID        NAME                CPU %               MEM USAGE / LIMIT    MEM %               NET I/O             BLOCK I/O           PIDS
8182db81301a        cpu_unlimit         198.54%             532.7MiB / 3.83GiB   13.58%              766B / 0B           0B / 0B             7

root@ubuntu:~# docker run -it --rm --name cpu_limit  --cpus 1 lorel/docker-stress-ng --cpu 2 --vm 2(--cpu 2 --vm 2为docker-stress-ng镜像的启动参数)
root@ubuntu:~# docker stats 
CONTAINER ID        NAME                CPU %               MEM USAGE / LIMIT    MEM %               NET I/O             BLOCK I/O           PIDS
33c521218a69        cpu_limit           100.66%             532.7MiB / 3.83GiB   13.58%              766B / 0B           0B / 0B             7

root@ubuntu:~# cat /sys/fs/cgroup/cpu,cpuacct/docker/33c521218a6969e62ea4986922c6f9dae0cf310a1be1ad509e6a06b1ddfadc35/cpu.cfs_quota_us
100000

#每核心 CPU 会按照 1000 为单位转换成百分比进行资源划分,2 个核心 的 CPU 就是 200000/1000=200%,4 个核心 400000/1000=400%,以此类推。

#常用的cpu限制选项:
--cpus #指定容器可以使用多少可用CPU资源,例如,如果主机有两个CPU,并且设置了--cpus =“1.5”,那么该容器将保证最多可以访问1.5个的CPU(如果是4核CPU,那么还可以是4核心上每核用一点,但是总计是1.5核心的CPU), 这相当于设置--cpu-period =“100000”和--cpu-quota =“150000”,--cpus 主要在 Docker 1.13 和更高版本中使用,目的是替代--cpu-period 和--cpu-quota 两个 参数,从而使配置更简单,但是最大不能超出宿主机的 CPU 总核心数(在操作系统看到的 CPU 超线程后的数值)。
--cpu-period #(CPU 调度周期)设置CPU的CFS调度程序周期,必须与 --cpu-quota 一起使用,默认周期为100微秒 (1Second=1000Millisecond=1000000Microsecond)。
--cpu-quota #在容器上添加 CPU CFS 配额,计算方式为 cpu-quota / cpu-period 的结果值,早期的 docker(1.12 及之前)使用此方式设置对容器的 CPU 限制值,新版本docker(1.13 及以上版本,通常使用--cpus 设置此值。)
--cpuset-cpus #用于指定容器运行的 CPU 编号,也就是我们所谓的绑核。

如对docker容器的内存使用大小的限制

#未限制内存,容器可使用宿主机的最大内存
root@ubuntu:~# cat /sys/fs/cgroup/memory/docker/b80df22af1dfdddf1e353e94316962aa801dedf201a847a7a6b29d311cca20c7/
cgroup.clone_children               memory.kmem.tcp.failcnt             memory.oom_control
cgroup.event_control                memory.kmem.tcp.limit_in_bytes      memory.pressure_level
cgroup.procs                        memory.kmem.tcp.max_usage_in_bytes  memory.soft_limit_in_bytes
memory.failcnt                      memory.kmem.tcp.usage_in_bytes      memory.stat
memory.force_empty                  memory.kmem.usage_in_bytes          memory.swappiness
memory.kmem.failcnt                 memory.limit_in_bytes               memory.usage_in_bytes
memory.kmem.limit_in_bytes          memory.max_usage_in_bytes           memory.use_hierarchy
memory.kmem.max_usage_in_bytes      memory.move_charge_at_immigrate     notify_on_release
memory.kmem.slabinfo                memory.numa_stat                    tasks
root@ubuntu:~# cat /sys/fs/cgroup/memory/docker/b80df22af1dfdddf1e353e94316962aa801dedf201a847a7a6b29d311cca20c7/memory.limit_in_bytes
9223372036854771712

#测试对容器使用的内存进行限制,测试宿主机为2C4G。
root@ubuntu:~# docker pull lorel/docker-stress-ng   #下载docker压测镜像
root@ubuntu:~# docker run -it --rm --name memory_unlimit lorel/docker-stress-ng --vm 2 --vm-bytes 256M 
root@ubuntu:~# docker stats 
CONTAINER ID        NAME                CPU %               MEM USAGE / LIMIT    MEM %               NET I/O             BLOCK I/O           PIDS
732513c5452c        memory_unlimit      198.29%             524.5MiB / 3.83GiB   13.37%              906B / 0B           0B / 0B             5

root@ubuntu:~# docker run -it --rm -m 256m --name memory_limit lorel/docker-stress-ng --vm 2  --vm-bytes 256M 
root@ubuntu:~# docker stats 
CONTAINER ID        NAME                CPU %               MEM USAGE / LIMIT   MEM %               NET I/O             BLOCK I/O           PIDS
75d2dc64def2        memory_limit        54.04%              253.9MiB / 256MiB   99.19%              766B / 0B           1.2GB / 1.54GB      5

root@ubuntu:~# cat /sys/fs/cgroup/memory/docker/75d2dc64def231086b17c425fd6a81271cd9f53240fd6752716e4deaff81e2f9/memory.limit_in_bytes
268435456


常用的内存限制参数:
-m or --memory #容器可以使用的最大内存量,如果设置此选项,则允许的最 小存值为4m (4 兆字节)。
--memory-reservation #允许指定小于--memory的软限制,当Docker检测到主机上的争用或内存不足时会激活该限制,如果使用--memory-reservation,则必须将其设置为低于--memory 才能使其优先。 因为它是软限制,所以不能保证容器不超过限制。
--oom-kill-disable #默认情况下,发生OOM时,kernel会杀死容器内进程,但是可以使用--oom-kill-disable 参数,可以禁止oom发生在指定的容器上,即仅在已设置-m/--memory选项的容器上禁用OOM,如果-m 参数未配置,产生OOM时,主机为了释放内存还会杀死系统进程。

3. 基于Docker File的镜像构建

Docker File是一种可以被Docker程序解释的脚本,直观展示了镜像产生的全过程。常见的命令如下:

ADD

  • 添加文件,自动解压
  • ADD 的优点:在执行 <源文件> 为 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,会自动复制并解压到 <目标路径>。
  • ADD 的缺点:在不解压的前提下,无法复制 tar 压缩文件。会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。具体是否使用,可以根据是否需要自动解压来决定。

COPY

  • 复制指令,从上下文目录中复制文件或者目录到容器里指定路径
  • COPY [–chown=:] <源路径1>… <目标路径>
  • COPY [–chown=:] ["<源路径1>",… “<目标路径>”]

ENV

  • 设置环境变量,定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。
  • 格式:
  • ENV
  • ENV = =…

ARG

  • 构建参数,与 ENV 作用一致。不过作用域不一样。ARG 设置的环境变量仅对 Dockerfile 内有效,也就是说只有 docker build 的过程中有效,构建好的镜像内不存在此环境变量。
  • 构建命令 docker build 中可以用 --build-arg <参数名>=<值> 来覆盖。
  • 格式:
  • ARG <参数名>[=<默认值>]

EXPOSE

  • 仅仅只是声明端口。
  • 作用:
  • 帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射。
  • 在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。
  • 格式:
  • EXPOSE <端口1> [<端口2>…]

FROM

  • 定制的镜像都是基于 FROM 的镜像,这里的 nginx 就是定制需要的基础镜像。后续的操作都是基于 nginx。

LABEL

  • 指令用来给镜像添加一些元数据(metadata),以键值对的形式,语法格式如下:
  • LABEL = = = …
  • 比如我们可以添加镜像的作者:
  • LABEL org.opencontainers.image.authors=“runoob”

STOPSIGNAL

  • 指令设置将发送到容器以退出的系统调用信号。这个信号可以是一个有效的无符号数字,与内核的syscall表中的位置相匹配,例如9,或者是SIGNAME格式的信号名,例如SIGKILL

USER

  • 用于指定执行后续命令的用户和用户组,这边只是切换后续命令执行的用户(用户和用户组必须提前已经存在)。
  • 格式:
  • USER <用户名>[:<用户组>]

VOLUME

  • 定义匿名数据卷。在启动容器时忘记挂载数据卷,会自动挂载到匿名卷。
  • 作用:
  • 避免重要的数据,因容器重启而丢失,这是非常致命的。
  • 避免容器不断变大。
  • 格式:
  • VOLUME ["<路径1>", “<路径2>”…]
  • VOLUME <路径>

WORKDIR

  • 指定工作目录。用 WORKDIR 指定的工作目录,会在构建镜像的每一层中都存在。(WORKDIR 指定的工作目录,必须是提前创建好的)。
  • docker build 构建镜像过程中的,每一个 RUN 命令都是新建的一层。只有通过 WORKDIR 创建的目录才会一直存在。
  • 格式:
  • WORKDIR <工作目录路径>

RUN

  • 用于执行后面跟着的命令行命令。有以下俩种格式:
  • shell 格式:
  • RUN <命令行命令> # <命令行命令> 等同于,在终端操作的 shell 命令。
  • exec 格式:
  • RUN [“可执行文件”, “参数1”, “参数2”]
  • 例如:
  • RUN ["./test.php", “dev”, “offline”] 等价于 RUN ./test.php dev offline

CMD

  • 类似于 RUN 指令,用于运行程序,但二者运行的时间点不同:
  • CMD 在docker run 时运行。
  • RUN 是在 docker build
  • CMD <shell 命令>
  • CMD ["<可执行文件或命令>","","",…]
  • CMD ["","",…] # 该写法是为 ENTRYPOINT 指令指定的程序提供默认参数

ENDPOINT

  • 类似于 CMD 指令,但其不会被 docker run 的命令行参数指定的指令所覆盖,而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序。
  • 但是, 如果运行 docker run 时使用了 --entrypoint 选项,将覆盖 CMD 指令指定的程序。
  • 优点:在执行 docker run 的时候可以指定 ENTRYPOINT 运行所需的参数。
  • 注意:如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最后一个生效。
  • 格式:
  • ENTRYPOINT ["","","",…]

3.1 使用Docker File制作Nginx镜像

root@ubuntu:~# docker pull alpine:3.13
root@ubuntu:~# mkdir dockerfile/{web/{nginx,tomcat,jdk},system/{centos,ubuntu,alpine}} -pv #按照业务或者系统类型进行分类管理,方便后期维护

root@ubuntu:~# cd ./dockerfile/web/nginx

root@ubuntu:~/dockerfile/web/nginx# vim Dockerfile
#My Nginx images base on apline Dockerfile
FROM alpine:3.13
LABEL maintainer="Eric_Zhang@123.com"
ENV NGINX_VERSION 1.21.3

ADD nginx-${NGINX_VERSION}.tar.gz /usr/local/src/
ADD web-statics.tar.gz /data/statics

#Make nginx modules
RUN set -x \
     && addgroup -g 1001 -S nginx \
     && adduser -S -D -H  -u 1001  -s /sbin/nologin -G nginx -g nginx nginx \
#Change apk source and install dependancy packages
     && sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories \
     && apk add --no-cache --virtual .bulid-deps \
       gcc \
       libc-dev \
       make \
       openssl-dev \
       pcre-dev \
       zlib-dev \
       linux-headers \
       libxslt-dev  \
       gd-dev \
       geoip-dev \
       perl-dev \
       libedit-dev \
       mercurial \
       bash \
       alpine-sdk \
       findutils \
     && export HOME=/usr/local/src/nginx-${NGINX_VERSION} \
     && cd ${HOME} \
     && ./configure --prefix=/apps/nginx \
         --with-http_sub_module \
     && make \
     && make install \
     && chown -R nginx:nginx /data/statics/ /apps/nginx/ \
     && ln -sv /apps/nginx/sbin/nginx /usr/sbin/nginx
ADD nginx.conf /apps/nginx/conf/nginx.conf
EXPOSE 80 443
ENTRYPOINT ["/usr/sbin/nginx"]
CMD  ["-g","daemon off;"]

root@ubuntu:~/dockerfile/web/nginx# docker build -t nginx-apline:v1 .
root@ubuntu:~/dockerfile/web/nginx# docker run -it -d -p 80:80 nginx-apline:v1

4. Docker数据的持久化

Docker镜像是分层设计的,镜像层是只读层,镜像启动容器后添加一层可读写的文件系统,可以写入用户数据,如果需要将数据永久保存,需要将容器中的数据保存到宿主机。Docker的数据类型分为两种:

  • 数据卷,类似于一块磁盘,是宿主机上的目录或者文件,可以直接被mount到容器中使用。使用场景:
  • 日志输出
  • 静态页面
  • 应用配置文件
  • 多容器间的目录或文件共享
#挂载宿主机目录
root@ubuntu:~# docker run -d --name vulume-mount -v /data/statics:/apps/nginx/statics/ -p 80:80 nginx-apline:v1   #默认为可读写挂载,-d 后台启动容器

root@ubuntu:~# docker run -d --name vulume-mount -v /data/statics:/apps/nginx/statics/:ro -p 80:80 nginx-apline:v1  #只读挂载


root@ubuntu:~# docker run -it -d -p 80:80 -v /data/conf:/apps/nginx/conf:ro -v /data/statics/:/apps/nginx/statics/ nginx-apline:v1  #多目录挂载
#挂载文件(如配置文件等)
root@ubuntu:~# docker run -it -d -p 80:80 -v /data/conf/nginx.conf:/apps/nginx/conf/nginx.conf:ro nginx-apline:v1
  • 数据容器,将宿主机的目录挂载至一个专门的数据卷容器,让其它容器通过数据卷容器读写宿主机的数据
#启动卷容器server(将宿主机的数据目录挂载至容器)
root@ubuntu:~# docker run -d --name volume-server -v /data/statics/:/apps/nginx/statics:ro nginx-apline:v1 

#启动cline端卷容器
root@ubuntu:~# docker run -d --name volume-client -p 80:80 --volumes-from volume-server nginx-apline:v1

5. 基于反向代理的Harbor镜像仓库高可用实现

5.1 安装harbor

#在具有docker环境的主机上安装docker-compose
root@ubuntu:~/docker_install# cp docker-compose-Linux-x86_64  /usr/bin/docker-compose
root@ubuntu:~/docker_install# chmod +x /usr/bin/docker-compose
root@ubuntu:~/docker_install# docker-compose --version
docker-compose version 1.24.1, build 4667896b

#下载harbor
wget https://github.com/goharbor/harbor/releases/download/v2.3.2/harbor-offline-installer-v2.3.2.tgz

#安装配置harbor
root@ubuntu:~# tar xvf harbor-offline-installer-v2.3.2.tgz  -C /usr/local/src/
root@ubuntu:~# ln -sv /usr/local/src/harbor /usr/local/

root@ubuntu:/usr/local/harbor# cp harbor.yml.tmpl harbor.yml 
root@ubuntu:/usr/local/harbor# grep "^[a-Z]" harbor.yml
hostname: 192.168.6.76
http:
harbor_admin_password: Harbor12345
database:
data_volume: /data/harbor
trivy:
jobservice:
notification:
chart:
log:
proxy:

root@ubuntu:/usr/local/harbor# ./install.sh 

#启动harbor
root@ubuntu:/usr/local/harbor# docker-compose start
Starting log         ... done
Starting registry    ... done
Starting registryctl ... done
Starting postgresql  ... done
Starting portal      ... done
Starting redis       ... done
Starting core        ... done
Starting jobservice  ... done
Starting proxy       ... done

#启动后更新配置
root@ubuntu:/usr/local/harbor# docker-compose stop
修改harbor.yml文件后,重新生成配置文件
root@ubuntu:/usr/local/harbor# ./prepare
root@ubuntu:/usr/local/harbor# docker-compose start

#安装镜像扫描模块,需要添加--with-trivy ,如果在后期添加过程中出现如下报错
root@ubuntu:/usr/local/harbor# docker-compose start
Starting log           ... done
Starting registry      ... done
Starting registryctl   ... done
Starting postgresql    ... done
Starting portal        ... done
Starting redis         ... done
Starting core          ... done
Starting jobservice    ... done
Starting proxy         ... done
Starting trivy-adapter ... failed 
#重新安装
root@ubuntu:/usr/local/harbor# ./install.sh --with-trivy

5.2 配置docker向harbor上传镜像

#编辑docker配置文件
root@ubuntu:/usr/local/harbor# vim /etc/docker/daemon.json
{
    "registry-mirrors": ["https://hcepoa2b.mirror.aliyuncs.com"],
    "storage-driver": "overlay2",
    "data-root": "/data/docker",
    "insecure-registries": ["192.168.6.76","192.168.6.77"]
}
#重启docker服务
root@ubuntu:/usr/local/harbor# systemctl restart docker

#docker登陆harbor
root@ubuntu:/usr/local/harbor# docker login 192.168.6.77
Username: admin
Password:

#在harbor中创建项目
#镜像打tag
root@ubuntu:/usr/local/harbor# docker tag apline:3.14 192.168.6.77/test/alpine:3.14
root@ubuntu:/usr/local/harbor# docker image list
REPOSITORY                      TAG                 IMAGE ID            CREATED             SIZE
192.168.6.77/test/alpine        3.14                14119a10abf4        12 days ago         5.6MB
alpine                          3.14                14119a10abf4        12 days ago         5.6MB
goharbor/harbor-exporter        v2.3.2              37f41f861e77        3 weeks ago         81.1MB
...

#上传测试
root@ubuntu:/usr/local/harbor# docker push 192.168.6.77/test/alpine:3.14

5.3 基于反向代理和harbor镜像复制的高可用

docker之间网络隔离 docker的六种隔离_docker之间网络隔离_07

以基于镜像复制的高可用为例进行说明:

5.3.1 配置Haproxy进行负载

root@ubuntu:~# vim /etc/haproxy/haproxy.cfg ...
listen harbor-80
  bind 192.168.6.77:8080  #生产环境使用VIP
  mode tcp
  balance source
  server harbor1 192.168.6.76:80 check inter 3s fall 3 rise 5
  server harbor2 192.168.6.77:80 check inter 3s fall 3 rise 5
...

root@ubuntu:~# systemctl restart haproxy 
root@ubuntu:~# ss -anlptu | grep 8080
tcp  LISTEN 0      128                        192.168.6.77:8080         0.0.0.0:*                     users:(("haproxy",pid=127954,fd=7))

5.3.2 配置Harbor镜像复制

在两台harbor上创建仓库管理目标

docker之间网络隔离 docker的六种隔离_docker之间网络隔离_08

docker之间网络隔离 docker的六种隔离_nginx_09

docker之间网络隔离 docker的六种隔离_ubuntu_10

在两台harbor主机上创建复制管理

docker之间网络隔离 docker的六种隔离_docker之间网络隔离_11

docker之间网络隔离 docker的六种隔离_docker_12