Featured image of post 在Docker容器中运行Docker

在Docker容器中运行Docker

本文介绍了如何在Docker容器中运行Docker

dind(docker in docker)

什么是 Docker in Docker

Docker in Docker(DIND)是一种在 Docker 容器中运行 Docker 引擎的方法。这种方法常用于测试、CI/CD 和开发环境中,以便在隔离的环境中运行 Docker 容器。例如,使用 Docker 容器来创建和管理其他容器,而无需直接依赖宿主机上的 Docker 引擎。

应用场景:

  • CI/CD:在持续集成(CI)和持续部署(CD)过程中,您可能需要构建 Docker 镜像,并将其推送到 Docker 注册中心。在这种情况下,可以使用 Docker in Docker 来在容器中运行 Docker 构建任务。
  • 测试和开发:开发人员可以在容器内运行 Docker,以测试和开发涉及多个容器的应用程序。
  • 资源隔离:通过在容器内运行 Docker,能够提供与宿主机隔离的资源和环境,避免与宿主主机上的其他进程或容器发生冲突。

在容器内运行 Docker 引擎需要容器拥有较高的权限。通常,Docker 容器默认是以非特权模式运行的,这意味着它们无法访问宿主机的某些资源和功能。为了能够在容器内运行 Docker,必须给容器授予特权模式--privileged),允许容器访问宿主机的所有设备、特权操作和内核功能。

启动docker in docker

在容器内运行docker,需要主机具有特权

准备所需的文件

dind-init.sh

该脚本是从github codespace中获取的,脚本主要包括清理旧的 PID 文件、挂载所需的文件系统、启用 cgroup 嵌套、配置 DNS、启动 Docker 守护进程。其中嵌套了dind脚本(一个由社区维护的docker in docker 配置脚本)

https://github.com/moby/moby/blob/master/hack/dind

#!/bin/sh
#----------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
#----------------------------------------------------------------------------------------------

set -e

AZURE_DNS_AUTO_DETECTION=false
DOCKER_DEFAULT_ADDRESS_POOL=
DOCKER_DEFAULT_IP6_TABLES=
dockerd_start="AZURE_DNS_AUTO_DETECTION=${AZURE_DNS_AUTO_DETECTION} DOCKER_DEFAULT_ADDRESS_POOL=${DOCKER_DEFAULT_ADDRESS_POOL} DOCKER_DEFAULT_IP6_TABLES=${DOCKER_DEFAULT_IP6_TABLES} $(cat << 'INNEREOF'
    # explicitly remove dockerd and containerd PID file to ensure that it can start properly if it was stopped uncleanly
    find /run /var/run -iname 'docker*.pid' -delete || :
    find /run /var/run -iname 'container*.pid' -delete || :

    # -- Start: dind wrapper script --
    # Maintained: https://github.com/moby/moby/blob/master/hack/dind

    export container=docker

    if [ -d /sys/kernel/security ] && ! mountpoint -q /sys/kernel/security; then
        mount -t securityfs none /sys/kernel/security || {
            echo >&2 'Could not mount /sys/kernel/security.'
            echo >&2 'AppArmor detection and --privileged mode might break.'
        }
    fi

    # Mount /tmp (conditionally)
    if ! mountpoint -q /tmp; then
        mount -t tmpfs none /tmp
    fi

    set_cgroup_nesting()
    {
        # cgroup v2: enable nesting
        if [ -f /sys/fs/cgroup/cgroup.controllers ]; then
            # move the processes from the root group to the /init group,
            # otherwise writing subtree_control fails with EBUSY.
            # An error during moving non-existent process (i.e., "cat") is ignored.
            mkdir -p /sys/fs/cgroup/init
            xargs -rn1 < /sys/fs/cgroup/cgroup.procs > /sys/fs/cgroup/init/cgroup.procs || :
            # enable controllers
            sed -e 's/ / +/g' -e 's/^/+/' < /sys/fs/cgroup/cgroup.controllers \
                > /sys/fs/cgroup/cgroup.subtree_control
        fi
    }

    # Set cgroup nesting, retrying if necessary
    retry_cgroup_nesting=0

    until [ "${retry_cgroup_nesting}" -eq "5" ];
    do
        set +e
            set_cgroup_nesting

            if [ $? -ne 0 ]; then
                echo "(*) cgroup v2: Failed to enable nesting, retrying..."
            else
                break
            fi

            retry_cgroup_nesting=`expr $retry_cgroup_nesting + 1`
        set -e
    done

    # -- End: dind wrapper script --

    # Handle DNS
    set +e
        cat /etc/resolv.conf | grep -i 'internal.cloudapp.net' > /dev/null 2>&1
        if [ $? -eq 0 ] && [ "${AZURE_DNS_AUTO_DETECTION}" = "true" ]
        then
            echo "Setting dockerd Azure DNS."
            CUSTOMDNS="--dns 168.63.129.16"
        else
            echo "Not setting dockerd DNS manually."
            CUSTOMDNS=""
        fi
    set -e

    if [ -z "$DOCKER_DEFAULT_ADDRESS_POOL" ]
    then
        DEFAULT_ADDRESS_POOL=""
    else
        DEFAULT_ADDRESS_POOL="--default-address-pool $DOCKER_DEFAULT_ADDRESS_POOL"
    fi

    # Start docker/moby engine
    ( dockerd $CUSTOMDNS $DEFAULT_ADDRESS_POOL $DOCKER_DEFAULT_IP6_TABLES > /tmp/dockerd.log 2>&1 ) &
INNEREOF
)"

sudo_if() {
    COMMAND="$*"

    if [ "$(id -u)" -ne 0 ]; then
        sudo $COMMAND
    else
        $COMMAND
    fi
}

retry_docker_start_count=0
docker_ok="false"

until [ "${docker_ok}" = "true"  ] || [ "${retry_docker_start_count}" -eq "5" ];
do
    # Start using sudo if not invoked as root
    if [ "$(id -u)" -ne 0 ]; then
        sudo /bin/sh -c "${dockerd_start}"
    else
        eval "${dockerd_start}"
    fi

    retry_count=0
    until [ "${docker_ok}" = "true"  ] || [ "${retry_count}" -eq "5" ];
    do
        sleep 1s
        set +e
            docker info > /dev/null 2>&1 && docker_ok="true"
        set -e

        retry_count=`expr $retry_count + 1`
    done

    if [ "${docker_ok}" != "true" ] && [ "${retry_docker_start_count}" != "4" ]; then
        echo "(*) Failed to start docker, retrying..."
        set +e
            sudo_if pkill dockerd
            sudo_if pkill containerd
        set -e
    fi

    retry_docker_start_count=`expr $retry_docker_start_count + 1`
done

# Execute whatever commands were passed in (if any). This allows us
# to set this script to ENTRYPOINT while still executing the default CMD.
exec "$@"

启动一个容器

以特权模式启动容器,并挂载我们准备的目录

  1. 以特权模式启动一个容器

    docker run -it --rm --privileged -v $(pwd)/dind-init.sh:/tmp/dind-init.sh --dns 8.8.8.8 ubuntu bash
    
  2. 安装 dockerd

    apt update && apt install -y curl
    curl -fsSL https://get.docker.com -o get-docker.sh
    sh get-docker.sh --mirror Aliyun
    

    如果在访问软件源时存在网络问题,可以先进行换源

    bash <(curl -sSL https://linuxmirrors.cn/main.sh)
    
  3. 执行dind-init.sh脚本

    chmod +x /tmp/dind-init.sh
    /tmp/dind-init.sh
    
  4. 测试dind正常运行

    docekr ps
    docker run -it --rm busybox curl example.com
    

result

潜在问题

性能开销

DIND 的一种常见问题是性能开销。由于 Docker 守护进程需要在容器内运行,且容器要访问宿主机的资源,可能会导致一定的性能下降。在 CI/CD 或测试环境中使用时,可能会影响构建速度。

权限问题

由于 Docker 守护进程需要访问宿主机的内核功能和设备(如挂载的 Docker socket 和网络设置),因此必须以特权模式运行容器。如果不使用 --privileged 参数,Docker 将无法正常工作。但是特权容器容易被恶意攻击,导致宿主机被恶意访问。

附录

封面由DALL生成,提示词如下:

A digital artwork of a large Docker whale, symbolizing the main Docker container, with a smaller Docker whale inside it, representing a containerized environment. The large whale has a playful, dynamic appearance with the smaller whale comfortably inside its body, showing the concept of Docker in Docker. The background features a soft, tech-inspired blue with abstract shapes resembling a cloud or network. The image should have a clean and minimalistic design with a high-tech, modern look. Lighting should highlight the whales, creating depth and focus. Created using: digital painting, soft gradients, high-definition quality, natural look, modern tech style, soft shadows, vivid colors, and sleek lines --ar 16:9