How To Use Docker For Networking And Security
作者:禅与计算机程序设计艺术
1.背景介绍
在虚拟化、容器化以及分布式架构领域中占据着关键地位的Docker技术,在其应用范围上虽已取得显著进展的同时,在网络安全层面的支持却仍有待加强。尽管该技术提供了诸如netns等相关的工具包来实现特定功能如局域网隔离与流量转发等操作但这些功能尚显局限无法满足企业级网络安全环境下的复杂需求因此如何更有效地利用Docker的技术资源来解决实际应用中的网络安全问题成为了众多技术开发者亟需深入研究的重要课题本文旨在通过结合作者多年的实践经验并参考当前Docker生态系统的最新动态向广大读者系统地介绍网络安全的基本概念核心技术和实用方案希望能为企业IT安全团队提供切实可行的技术参考与指导方案
2.核心概念与联系
2.1 Docker 网络模式
Docker 的网络模式(Network Mode)不仅用于指定容器的网络配置,并且通过这种方式能够实现对容器间通信的管理与控制。Docker 提供了三种不同的网络模式选项:包括 host 模式(默认选择)、bridge 模式以及 overlay 模式。
- 宿主模式 宿主模式通过宿主机的网络命名空间(netns)实现连接。这种配置下提供的IP地址范围与宿主机完全一致。
 - 桥接模式 该方法通过Linux桥接设备实现虚拟化网卡配置。
 - Docker Swarm支持的通信机制是Overlay 模式的一种实现方式。
 
2.2 Docker 服务发现
Docker 在采用 DNS 模式的前提下提供服务定位机制,在容器配置中可通过指定 --dns 参数来明确指定 DNS 服务器地址的同时支持通过网络接口自动生成相应的 DNS 注册信息。
此外, Docker 可以利用标签来指定服务类型.这样就可以通过域名的方式访问不同的容器.例如,在配置一个 app=web 标签后,在 web.example.com 域名下即可访问该容器.
这些包括 Docker 网络与安全领域的核心知识点及其相关术语,在后续内容中我们将继续深入探讨其他相关知识
3.核心算法原理和具体操作步骤以及数学模型公式详细讲解
3.1 设置容器网络模式
配置容器的网络模式可通过docker运行命令中的--network=参数来实现。例如以下命令生成一个命名为my_container的容器,并将其指定为bridge模式:
    docker run -itd --name my_container --network=bridge nginx:latest
    
    
    代码解读
        执行完毕后,在查看容器的网络信息时可以看出, my_container 的 IP 地址是随机的:
    $ docker inspect my_container | grep \"IPAddress\"
            "SecondaryIPAddresses": null,
            "IPAddress": "",
                    "IPAddress": "172.17.0.2",
    
      
      
      
    
    代码解读
        3.2 设置静态 IP 地址
要给容器指定固定的 IP 地址,可以使用 docker network connect 命令:
    docker network create mynet
    docker container run -itd --name my_container --network=mynet --ip 192.168.1.10 nginx:latest
    docker network connect mynet second_host
    
      
      
    
    代码解读
        在配置完成后,在网络 mynet 中会设置 my_container 的固定 IP 地址 192.168.1.10。
当需要向容器配置多个 IP 地址时,在启动过程中可以一次性指定多个 –ip 选项来完成配置,请参考以下示例:
    docker container run -itd --name my_container --network=mynet --ip 192.168.1.10 --ip 192.168.1.20 nginx:latest
    
    
    代码解读
        这种方式可以方便地实现负载均衡,或是为不同应用设置不同的 IP 地址。
3.3 配置防火墙规则
有时,在实际应用中我们可能会采取措施来限制容器之间的通信流量以确保系统的安全性为此我们可以采用以下两种方法进行控制
3.3.1 添加 iptable 规则
Linux 操作系统赋予了防火墙规则以丰富的多样性。例如,在设置时可以选择允许特定协议类型、可配置的源IP地址及目的IP地址等参数组合。通过iptables命令设置防火墙规则即可实现对容器间通信的严格控制。
首先,查看容器的 IP 地址:
    $ docker inspect my_container | grep \"IPAddress\"
            "IPAddress": "192.168.1.10",
    
      
    
    代码解读
        然后,用 iptables 命令允许 TCP 流量,限制对 192.168.1.10 的访问:
    iptables -A INPUT -s 192.168.1.10 -p tcp -j ACCEPT
    iptables -A OUTPUT -d 192.168.1.10 -p tcp -j ACCEPT
    
      
    
    代码解读
        最后提醒,在使用 Docker 运行容器时,请确保在启动命令中包含 --network=none 以避免容器获得 IP 地址。
    docker run -itd --name my_container --network=none nginx:latest
    
    
    代码解读
        这样,容器只能被内部网络访问,而不能被外界访问。
3.3.2 使用 Docker 的 DNS 模式
除了限制不同容器间的通信外,在Docker中还可以配置DNS模式。DNS 作为一种存储主机名与其IP地址对应关系的分布式数据库。在访问其他Docker容器时可通过其名称而非IP地址来检索DNS记录。
为了启用 Docker DNS 模式,在 Docker daemon 的配置文件中添加 'dns' 配置项:
    {
      "dns": ["192.168.3.11"]
    }
    
      
      
    
    代码解读
        注意,修改配置文件后,要重启 Docker daemon 才能生效。
然后,在自定义的 /etc/hosts 文件以及并不仅仅局限于其他途径中
168.1.10   my_container
    192.168.1.20   other_container
    
      
    
    代码解读
        通过这种方式, 就可以用我的容器地址来实现对我的容器的接入, 也可以用其他容器的地址来实现对其他容器的接入
3.4 数据加密传输
在互联网上传输的数据都有可能遭受中间人攻击。为此,我们应当保证数据在传输过程中是加密的。
如今广泛采用的两种加密技术是SSL和TLS。尽管早期版本虽然极具吸引力但已不再被推荐使用而TLS则是一个经过标准化的协议具有极高的安全性
必须在 Dockerfile 或 Docker Compose 文件中安装 openssl 包,并且可以参考以下命令来生成证书。
    openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout example.com.key -out example.com.crt
    
    
    代码解读
        然后,在 Docker Compose 文件中定义如下几个卷:
    version: '3'
    services:
      web:
    image: nginx:latest
    volumes:
      -./certs:/usr/local/nginx/conf/ssl/
    environment:
      - NGINX_PORT=443
    ports:
      - "${NGINX_PORT}:${NGINX_PORT}"
    volumes:
      certs:
    
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读
        其中用于存储生成的证书文件的卷是certs。接下来,在nginx.conf文件中将这些证书文件作为引用使用。
    server {
      listen       443 ssl;
    
      # PEM encoded server certificate and key file paths
      ssl_certificate      /usr/local/nginx/conf/ssl/example.com.crt;
      ssl_certificate_key  /usr/local/nginx/conf/ssl/example.com.key;
    
      root   /usr/share/nginx/html;
      index  index.html index.htm;
    }
    
      
      
      
      
      
      
      
      
      
    
    代码解读
        通过这种方式,在线的所有 HTTP 请求都会被自动地引导至 HTTPS 网络上,并通过数字证书来进行加密传输。
3.5 不可信任的镜像
Docker在镜像拉取过程中,默认会对获取的镜像进行数字签名验证,并以防止镜像被篡改。然而,并非所有厂商都会遵循Docker的规定,在这种情况下他们可能会发布未经官方认证的镜像文件,并且还可能使用自身私钥生成的签名镜像文件。
面对那些值得怀疑的镜像,可以选择人工查看其签名,并禁用数字签名验证:
    docker pull my_image
    docker tag my_image my_registry.com/my_image
    docker rmi $(docker images -q)
    sed -i '/^# signature-/s/^#//' /etc/docker/daemon.json
    systemctl restart docker
    
      
      
      
      
    
    代码解读
        第一个操作用于从远程获取镜像;第二个操作重新为镜像打标签;第三个操作清除本地所有的镜像;第四个操作阻止配置文件中的数字签名验证行;第五个操作重启Docker服务。这样就能确保正常地获取不可信赖的镜像。
3.6 CRIU 快照隔离
CRIU 是一个开源项目,在其功能实现中提供了快照隔离机制。具体而言,在使用 checkpoint 和 restore 功能时,在完成一次隔离操作后会生成一个完整的快照副本,并在下次启动时自动将其恢复到该快照的状态。
借助 CRIU 的功能,在不干扰容器内业务的前提下(即可实现)] 容器的快速暂停与恢复功能(即达到短暂隔离服务的目的)。例如,在容器出现异常终止状态(即处于不可中断服务状态)时(可用该技术进行操作),可以通过调用 CRIU 进行暂停操作;在重启时再调用 CRIU 恢复至初始状态(从而保证服务连续性)。
为了实现快照隔离功能的达成, 容器内的进程和资源必须具备某种机制的支持. 这意味着在特定条件下(例如容器内仅运行一个程序时), 可以尝试采用其他方法来实现隔离, 例如通过 namespaces 和 cgroups 等手段进行操作.
3.7 深入分析容器网络
本节主要通过 Kubernetes 为例 展开讨论 Docker 的网络架构及其实现机制
3.7.1 Pod 中的网络
Kubernetes将一个逻辑单元命名为Pod,在其中的容器拥有与之相关的netns和网络架构。
每个Pod只有一个独特的IP地址,并可通过Service对象进行暴露。Service对象则提供了一种统一且负载均衡的方式供外界访问服务功能。
每个Pod都可以配置多台容器;但这些容器必须属于同一个Namespace,并且这样才能确保它们能够共享相同的网络配置。
3.7.2 CNI 插件
CNI 插件(Container Network Interface Plugin)主要负责为Pod分配IP地址以及相关的网络资源,并预先配置相应的路由信息表。
Kubernetes采用CNI插件,并通过调用一系列网络插件来实现容器间的网络管理功能。当前广泛使用的此类插件包括Flannel、Calico、Weave Net等。
Flannel 是专为 Kubernetes 开发的一个卓越的 CNI 插件。它包含两大核心组件:flannled 和 vxlan。vxlan 是一种基于端到端通信的虚拟专用网技术,并由 flannled 构建。flannled 定期监控 etcd 的状态变化,并根据集群内各节点的网络状况实时分配子网段并设置相应的路由规则。
Calingo 基于 SDN 原则设计,并不依赖 vxlan 资源转发协议;该方案采用了 BGP 协议作为其通信基础;提供了一个独立的控制器组件 caligo-node,并与 Kubernetes 共享部署环境;该控制器组件专门负责网络资源的动态分配以及相关策略的制定。
Weave Net is also a CNI plugin built on libnetwork. It employs a network architecture design similar to Calico's approach. Within its network namespace structure, each container has its own dedicated veth interface connected to a Linux bridge via the bridge for forwarding packets when exchanging data frames.
3.7.3 Container Runtime 组件
Kubernetes 中的 Container Runtime 组件负责启动和管理 Pod 中的容器。
最初只能采用 Docker 基础构建的容器运行时,在 Kubernetes 发展初期阶段主要依赖于 Docker 技术的支持。然而随着技术的发展和多样化需求的增长 Kubernetes 社区逐渐认识到 在采用其他技术栈的情况下构建兼容的容器运行时也是可行的 基于此决定制定通用的 CRI (Container Runtime Interface)
CRI 规定了 gRPC API 的规范性接口(API),从而使得所有支持 CRI 的底层容器运行时(例如 Docker Engine)能够借助该 API 实现与 Kubernetes 的交互。
ContainerD 作为新的 Kubernetes 标准容器运行时组件,在采用完全不同技术基础的同时实现了极高的远程镜像仓库拉取效率,并具备实时磁盘卷快照的能力。其运行机制类似于 Docker 但在操作速度和资源利用率上均超越了后者
ContainerD 作为新的 Kubernetes 标准容器运行时组件,在采用完全不同技术基础的同时实现了极高的远程镜像仓库拉取效率,并具备实时磁盘卷快照的能力
3.7.4 CRIU 快照隔离
CRIU 是一个 open-source 项目, 能够支持容器的快照隔离机制, 即能够捕捉当前容器某个时刻的状态并建立一个全新的隔离环境用于恢复到该状态.
主要优势在于能够防止由于容器内部程序错误所带来的潜在风险或故障因素的影响,并且还能确保容器的高度可靠。
Kubernetes 采用基于快照的隔离策略,在实际运行中能够实现对运行环境的有效保护。具体而言,在完成服务挂载操作后系统会自动触发 CRIU 快照服务的创建过程。该过程中 Kubernetes 管理节点(kubelet)会向运行时系统发送相关指令,在停止服务之前将当前运行状态进行捕获并存储到存储介质中。当 Kubernetes 管理节点决定重启服务时,则会被触发恢复操作流程
实际上,在kubelet发送保存信号的时候...也不会立即启动快照流程...kubelet会在此期间稍作等待...以避免程序运行中的错误或异常终止...然而在这一段时间内仍可接收用户的请求以便在规定时间内完成任务。
3.7.5 IPTables
IPTables 是 Linux 核心中的一个网络流量控制列表模块,在处理数据包时依据预设规则进行处理。每当有新的数据包到达时,在入口端会按照预先定义好的顺序逐一检查每一条控制规则;系统会按照预设顺序逐一检查每一条控制指令;最终将根据第一条匹配到的相关规则对数据包进行相应的处理操作;最终将根据第一条匹配到的相关规则对数据包进行相应的处理操作;最终将根据第一条匹配到的相关规则对数据包进行相应的处理操作;最终将根据第一条匹配到的相关规则对数据包进行相应的处理操作
当容器启动时,kubelet 负责设置相应的 IPTables 规则以实现路由连接到主机与容器之间的关系。这些 IPTables 规则通常阻止外部网络的访问,并仅允许内部通信。
在容器与外部进行通信时,在线性队列(IPTables)中设置相应的路由规则以实现数据包的发送至Kubernetes Proxy服务器(Kube Proxy)。接着根据Service配置信息确定对应的Endpoint对象,并借助VirtualServer将数据包发送至Endpoint位置完成传输过程。
必须注意的一点是:由于容器共享了宿主系统的网络命名空间, container间仍需依靠宿主系统中的IPTables功能进行通信. 因此, 在部署时应当避免将任何容器暴露在公共网络中.
4.具体代码实例和详细解释说明
4.1 部署 Nginx 服务
    apiVersion: apps/v1beta1
    kind: Deployment
    metadata:
      name: my-nginx
    spec:
      replicas: 2
      template:
    metadata:
      labels:
        app: my-nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: my-service
    spec:
      type: ClusterIP
      selector:
    app: my-nginx
      ports:
      - port: 80
    targetPort: 80
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读
        该YAML文件定义了一个部署方案(Deployment),用于配置并运行两个Nginx实例,并引入一个服务(Service)以暴露访问接口。在部署方案中指定的参数表明:将容器的本地端口80映射到容器的外网80号端口;而服务端口映射则将服务端口80直接绑定到外部可用地址。
可以用以下命令来创建对象:
    kubectl apply -f deploy.yaml service.yaml
    
    
    代码解读
        4.2 实现负载均衡
为了达到负载均衡的目的,在Deployment中只需增加多个容器就可。例如,可以通过复制Deployment副本的方式来实现。
除此之外还可以通过 Service 的 spec.type 属性定义其类型 例如设置为 NodePort 或 LoadBalancer 等 使得该Service能够被集群外部的客户端访问
4.3 设置静态 IP 地址
当部署创建时,在 podTemplateSpec 中通过设置 IP 属性,并将其部署至特定的机器。
也可以通过修改 Service 的 annotations 属性来设置 IP 地址。
    apiVersion: v1
    kind: Service
    metadata:
      name: my-service
      annotations:
    service.alpha.kubernetes.io/tolerate-unready-endpoints: true
    # 添加注解
    kubernetes.io/ingress.class: traefik
    spec:
      externalTrafficPolicy: Local
      type: LoadBalancer
      loadBalancerIP: 10.240.0.15
      selector:
    app: my-nginx
      ports:
      - port: 80
    targetPort: 80
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读
        字段 annotations 的值等于 traefik 代表通过 Traefik Ingress Controller来负责处理来自客户端的请求
4.4 数据加密传输
首先,在位于 Web 服务集群中的 Kubernetes 环境中安装 OpenSSL 包,并生成相应的密钥对及其证书文件。
说明
    volumeMounts:
    - name: cert-dir
      mountPath: "/usr/local/nginx/conf/ssl/"
    ...
    volumes:
    - name: cert-dir
      emptyDir: {}
    
      
      
      
      
      
      
    
    代码解读
        Web 服务所在的模板文件需要修改,添加以下配置项:
    env:
    - name: SSL_CERT_DIR
      value: "/usr/local/nginx/conf/ssl"
    ...
    ports:
    - name: https
      containerPort: 443
    ...
    volumes:
    - name: default-token-zldlz
      secret:
    secretName: default-token-zldlz
    - name: cert-dir
      emptyDir: {}
    
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读
        在完成所有设置后,在 Web 服务相关的配置文件(如 nginx.conf)中新增并导入SSL配置项,并引用之前生成的CA证书文件
    listen       443 ssl;
    ssl on;
    ssl_certificate      /usr/local/nginx/conf/ssl/example.com.crt;
    ssl_certificate_key  /usr/local/nginx/conf/ssl/example.com.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ...
    location / {
    proxy_pass http://backend/;
    }
    
      
      
      
      
      
      
      
      
      
    
    代码解读
        这样,Web 服务就支持 HTTPS 协议,而且数据传输过程加密传输。
4.5 不可信任的镜像
Kubernetes 集群的运行可能因缺少足够的签名验证而面临难以防御的攻击威胁。例如,在实际操作中, 攻击者可能会采取以下措施:盗取私人镜像、篡改镜像内容以及伪造镜像签名等.
解决方案是将Notary(Docker镜像签名认证工具)集成到集群中,并针对镜像的拉取、推送等操作执行校验。该解决方案能够提供全面且高度可靠的镜像签名机制,并确保系统免受恶意篡改行为的影响
具体操作步骤如下:
Notary程序可通过以下方式获取:通过HTTP下载链接wget https://github.com/theupdateframework/notary/releases/download/v0.6.1/notary-Linux-amd64
- 
生成根密钥:
./notary-Linux-amd64 key generate root - 
调整Notary服务配置参数,并设置环境变量如下:
将Root Password环境变量设置为私有容器文件的内容,
并设置NOTARY_AUTH环境变量为password。
代码块保持不变:export NOTARY_ROOT_PASSWORD=$(cat ~/.docker/trust/private) && export NOTARY_AUTH=password 
运行一个Notay服务并执行以下命令:./notay-Linux-amd64 signer -config notay.json -secure false
- 
修改 Docker daemon 配置,添加签名选项:
{"signature-verification":true} - 
重启 Docker 服务:
sudo systemctl restart docker 
这样,集群就可以使用 Notary 来校验镜像的签名。
