Docker Desktop for mac 端口转发(port forwarding)

在完成 在 kind 部署MetalLB 之后,也就已经实现了标准Kubernets集群应用部署的输出了。但是对于 Docker Desktop网络 限制,实际上整个Kubernets都是在一个Linux虚拟机中运行,从物理主机 macOS 上不能直接访问这个输出地址,还需要做一个端口转发(port forwarding)来访问。

备注

X86移动云Kind(本地docker模拟k8s集群) ,采用直接在Linux物理主机上部署 kind(本地docker模拟k8s集群) 就没有这个麻烦,外部可以直接访问

解决方案

备注

另一种方式是采用 Docker环境运行Squid 运行反向代理服务器来实现端口转发

部署

构建 fedora-gw 镜像的Dockerfile
# USE DOCKER BUILD
# docker build --rm -t fedora-gw .
# USE DOCKER RUN
# docker run -itd --privileged=true -p 1122:22 --hostname fedora-gw --name fedora-gw fedora-gw

# USE nerdctl (containerd) BUILD
# nerdctl build -t fedora-gw .

# INTERACT RUN
# nerdctl run -it --privileged=true -p 1122:22 --hostname fedora-gw --name fedora-gw fedora-gw:latest

# BACKGROUND RUN
# nerdctl run -d --privileged=true -p 1122:22 --hostname fedora-gw --name fedora-gw fedora-gw:latest

FROM fedora:latest
MAINTAINER vincent huatai <vincent@huatai.me>

ENV container docker

# set china repo: mirros.163.com
RUN cp -R /etc/yum.repos.d /root/yum.repos.d
RUN rm /etc/yum.repos.d/fedora-cisco-openh264.repo
RUN sed -i 's/metalink=/#metalink=/g' /etc/yum.repos.d/*
RUN sed -i 's/#baseurl=/baseurl=/g' /etc/yum.repos.d/*
RUN sed -i 's/download.example\/pub\/fedora\/linux/mirrors.163.com\/fedora/g' /etc/yum.repos.d/*
RUN cp /root/yum.repos.d/fedora-cisco-openh264.repo /etc/yum.repos.d/

RUN dnf clean all
RUN dnf -y update

# Add Tini
ENV TINI_VERSION v0.19.0
# 标准方法是采用ADD方式向镜像添加tini,但是GFW阻碍,改为下载后本地复制
#ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
COPY tini /tini
RUN chmod +x /tini
ENTRYPOINT ["/tini", "--"]

# Copy tini entrypoint script
COPY entrypoint_ssh_cron_bash /entrypoint.sh
RUN chmod +x /entrypoint.sh

# not need systemd(initscripts)
RUN dnf -y install which sudo passwd openssh-clients openssh-server \
    iproute net-tools bind-utils bzip2 tmux sysstat nfs-utils lsof \
    procps tree file mlocate rsync cronie cronie-anacron \
    iptables-services

# add account "admin" and give sudo privilege
RUN groupadd -g 505 admin
RUN useradd -g 505 -u 505 -d /home/admin -m admin
RUN usermod -aG wheel admin
RUN echo "%wheel        ALL=(ALL)       NOPASSWD: ALL" >> /etc/sudoers

# set TIMEZONE to Shanghai
RUN unlink /etc/localtime && ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

# Add ssh public key for login
RUN mkdir -p /home/admin/.ssh
COPY authorized_keys /home/admin/.ssh/authorized_keys
RUN chown -R admin:admin /home/admin/.ssh
RUN chmod 600 /home/admin/.ssh/authorized_keys
RUN chmod 700 /home/admin/.ssh

RUN ssh-keygen -A

# run service when container started - sshd
EXPOSE 22:122

# Run your program under Tini
# CMD ["/your/program", "-and", "-its", "arguments"]
CMD ["/entrypoint.sh"]

备注

镜像 从Dockerfile构建Docker镜像 添加了 iptables 工具( iptables-services )以及必要的运维工具

  • 构建 fedora-gw 镜像:

构建 fedora-gw 镜像
docker build --rm -t fedora-gw .
  • 基于 fedora-gw 镜像运行 dev-gw 容器 :

基于 fedora-gw 镜像运行 dev-gw 容器,将host主机的 10000-10099 端口全部映射到这个网关容器
docker run -itd -p 122:22 -p 10000-10099:10000-10099 --network kind \
        --cap-add=NET_ADMIN --cap-add=NET_RAW \
        --hostname dev-gw --name dev-gw fedora-gw

备注

Docker支持端口范围的Port Mapping,不需要一个个映射可以方便这个 fedora-gw 容器内部自行进行端口转发。但是,我发现端口映射会拖慢Docker容器启动的速度(例如我尝试映射999个端口),所以我最终改为只映射99个端口(小型开发测试环境足够了)

备注

在容器内部使用 iptables 需要在运行容器时添加参数 --cap-add=NET_ADMIN--cap-add=NET_RAW (从 Docker 1.2开始支持)

检查和验证

  • 检查运行起来的 dev-gw 可以看到运行的容器实现了 10000-10099 的端口范围映射,并且 ssh -p 122 从host主机能够登陆到 dev-gw 容器中 :

检查 dev-gw 容器
% docker ps
CONTAINER ID   IMAGE                                COMMAND                  CREATED        STATUS        PORTS                                                                NAMES
78131fbac6e8   fedora-gw                            "/tini -- /entrypoin…"   2 hours ago    Up 2 hours    122/tcp, 0.0.0.0:10000-10099->10000-10099/tcp, 0.0.0.0:122->22/tcp   dev-gw

% ssh -p 122 admin@127.0.0.1
Last login: Tue Jan 31 18:59:02 2023 from 172.22.0.1
[admin@dev-gw ~]$
[admin@dev-gw ~]$ df -h
Filesystem      Size  Used Avail Use% Mounted on
overlay          59G   25G   31G  45% /
tmpfs            64M     0   64M   0% /dev
shm              64M     0   64M   0% /dev/shm
/dev/vda1        59G   25G   31G  45% /etc/hosts
tmpfs           3.9G     0  3.9G   0% /sys/firmware
配置 fedora-dev-tini 设置LoadBalancer服务类型后 kubectl get services 显示服务具备了 EXTERNAL-IP
% kubectl get svc
NAME                 TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)                                   AGE
fedora-dev-service   LoadBalancer   10.96.175.32    172.22.255.201   22:32440/TCP,80:31218/TCP,443:32049/TCP   4d1h

端口转发

  • dev-gw 内部执行 iptables 进行端口转发:

dev-gw 容器内执行iptables转发端口
#!/usr/bin/env bash

# 需要在host主机(也就是Docker 虚拟机)内部执行以下这行命令激活内核IP转发
#echo 1 > /proc/sys/net/ipv4/ip_forward

iptables -F
iptables -t nat -F
iptables -X

dev_gw="172.22.0.12"

fedora_dev="172.22.255.201"
fedora_dev_ssh="10001"

iptables -t nat -A PREROUTING -p tcp --dport ${fedora_dev_ssh} -j DNAT --to-destination ${fedora_dev}:22
iptables -t nat -A POSTROUTING -p tcp -d ${fedora_dev} --dport 22 -j SNAT --to-source ${dev_gw}

备注

这里我遇到一个错误,在容器内部无法修改 /proc/sys/net/ipv4/ip_forward ,提示报错:

iptables_port_forwarding: line 3: /proc/sys/net/ipv4/ip_forward: Read-only file system

Docker虚拟机内核参数必须在host主机,也就是 Docker Desktop on Mac 虚拟机 内修改

通过nsenter进入运行容器的控制台
docker run -it --privileged --pid=host debian nsenter -t 1 -m -u -n -i sh

在Docker VM内部执行以下命令开启内核IP转发:

echo 1 > /proc/sys/net/ipv4/ip_forward

此时就完成了所有端口转发的配置,从Host主机访问本机(所有网络接口)的端口 10001 都会被 Docker Desktop 映射到 dev-gw 虚拟机,然后又被 dev-gw 端口转发给目标 在 kind 部署MetalLB 对外提供的 fedora-dev 虚拟机的 fedora-dev-service 服务上(对外提供了多个服务端口)。整个过程虽然繁复,但是能够真正实现访问 kind(本地docker模拟k8s集群) 集群提供的Kubernetes服务,和生产环境没有差别。

改进

  • 为了方便快捷完成端口转发,修订运行 dev-gw 容器的命令,将 iptables_port_forwarding 脚本直接 bind 到容器内部,这样随时可以在物理主机上修改好脚本,只要重新创建一次容器就可以运行了:

dev-gw 容器运行时bind mount进端口转发脚本,方便自动执行
docker run -itd -p 122:22 -p 10000-10099:10000-10099 --network kind \
        --cap-add=NET_ADMIN --cap-add=NET_RAW \
        --mount type=bind,source="$(pwd)"/iptables_port_forwarding,target=/root/iptables_port_forwarding,readonly \
        --hostname dev-gw --name dev-gw fedora-gw

下一步

接下来需要解决如何将物理主机映射到 Docker Desktop 内部并被 kind(本地docker模拟k8s集群) 运行的容器挂载,这样就能够方便在物理主机上保存数据,实现在Kubernetes容器中进行各种开发和模拟,数据不会丢失:

参考