从Dockerfile构建Docker镜像
Dockerfile
在 Docker容器分配静态IP 和 Docker容器中运行ssh服务 我们实现了启动一个富容器(包含了SSH和很多必要工具)、设置静态IP地址、启动时运行多个服务(例如SSH),但是每次去手工从官方镜像开始处理容器效率低下,而自定义镜像也至少需要相对麻烦的手工处理。Docker提供了通过Dockerfile方式构建镜像,类似脚本方式,也就容易实现重放(在脚本基础上改进)。
Dockerfile就是包含用户调用命令行来组装镜像所有命令的一个文本文档,并且可以通过 docker build
来使用这个文档自动创建镜像。
docker build .
这个build过程将通过Docker daemon运行,而不是命令行。最好在一个包含Dockerfile的独立目录中运行上述命令。
警告
不要在根目录下运行 docker build .
,这会导致把整个硬盘内容传输给Docker daemon。
备注
如果目录下有多个文件,为了加快build,可以过滤掉目录下的不需要传递的文件,采用一个 .dockerignore
文件(类似 .gitignore
。
docker build参数
-f /path/to/a/Dockerfile
传递Dockerfile文件名,这样可以不用默认的Dockerfile
作为文件名,例如:docker build -f ubuntu18.04-ssh .
也可以使用如下方法(只传递Dockerfile,不传递目录中的文件):
docker build - < ubuntu18.04-ssh
-t 标签
,并且可以使用多个-t
参数来标记镜像属于多个镜像仓库,例如:docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .
备注
将 authorized_keys
公钥文件存放到当前目录下,然后再执行 docker build
命令以构建时把公钥复制到容器内部。
当使用 -f
参数指定Dockerfile来build镜像时,创建的镜像是没有 REPOSITORY
和 TAG
的,所以引用起来非常不便,类似:
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 845219589871 12 hours ago 277MB
强烈 建议结合 -f
和 -t
参数,这样就可以对镜像标记:
docker build -f ubuntu18.04-ssh -t local:ubuntu18.04-ssh .
docker build -f centos8-ssh -t local:centos8-ssh .
这样创建的镜像具有仓库和标签,显示如下:
REPOSITORY TAG IMAGE ID CREATED SIZE
local ubuntu18.04-ssh 1db298cb83f2 2 hours ago 278MB
local centos8-ssh 389c6ed34b34 16 minutes ago 286MB
备注
从 18.09 版本开始,Docker支持在后台执行build过程,这样可以并行执行多个build,并且可以检测并跳过不实用对build状态。这个实现是通过 moby/buildkit 项目实现对。要使用 BuildKit 后端,需要设置环境变量 DOCKER_BUILDKIT=1
,然后再执行 docker build
。
集成SSH的镜像制作Dockerfile
备注
Dockerfile案例分为Ubuntu和CentOS
# Build ubuntu18.04 image with ssh:
# ---------------------------------
# docker build -f ubuntu18.04-ssh -t local:ubuntu18.04-ssh .
# create container:
# -----------------
# docker run -itd -p 1122:22 --hostname myserver --name myserver local:ubuntu18.04-ssh
# create container with volume:
# -----------------------------
# docker volume create data
# docker run -itd -p 1122:22 --hostname myserver --name myserver -v data:/data local:ubuntu18.04-ssh
# create container with volume and static ip:
# -------------------------------------------
# docker volume create data
# docker network create --subnet=172.18.0.0/16 data-net
# docker run -itd -p 1122:22 --hostname myserver --name myserver -v data:/data \
# --net data-net --ip 172.18.0.11 local:ubuntu18.04-ssh
# create container with volume and static ip, then limit resource and map port:
# -----------------------------------------------------------------------------
# docker volume create data
# docker network create --subnet=172.18.0.0/16 data-net
# docker run -itd -p 1122:22 --hostname myserver --name myserver -v data:/data \
# --net data-net --ip 172.18.0.11 --memory=2048M --cpus="1.5" \
# -p 2211:22 local:ubuntu18.04-ssh
FROM docker.io/ubuntu:latest
MAINTAINER vincent huatai <vincent@huatai.me>
RUN apt update
RUN apt -y upgrade
RUN apt -y install openssh-server iproute2 net-tools sudo vim screen dnsutils gnupg iputils-ping
# Prepare sshd host key
RUN ssh-keygen -A
RUN mkdir /run/sshd
# add account "huatai" and give sudo vprivilege
# groupid 20(dialout=>staff) userid 501
RUN groupdel staff
RUN groupmod --new-name staff dialout
RUN useradd -g 20 -u 501 -d /home/huatai -m -s /bin/bash huatai
RUN usermod -aG sudo huatai
RUN sed -i '/^%sudo/d' /etc/sudoers
RUN echo "%sudo ALL=(ALL:ALL) NOPASSWD:ALL" >> /etc/sudoers
# Add ssh public key for login
RUN mkdir -p /home/huatai/.ssh
COPY authorized_keys /home/huatai/.ssh/authorized_keys
RUN chown -R huatai:staff /home/huatai/.ssh
RUN chmod 600 /home/huatai/.ssh/authorized_keys
RUN chmod 700 /home/huatai/.ssh
# run service when container started - sshd
EXPOSE 22:1122
#CMD ["/usr/sbin/sshd", "-D"]
# ----------
# WANT run sshd and get a bash
# ENTRYPOINT will not be override by commandline
# ----------
ENTRYPOINT /usr/sbin/sshd && /bin/bash
备注
安装了openssh服务之后,需要创建主机key才能启动服务,所以需要执行
RUN ssh-keygen -A
将host主机上
authorized_keys
复制到镜像中,这里可以使用COPY
指令或ADD
指令。推荐使用COPY
(ADD
指令用于添加tar.gz
文件,可以自动展开:ADD rootfs.tar.xz /
) 。请参考 Dockerfile COPY vs ADD: key differences and best practicesEXPOSE
指令用来将指示容器中运行端口输出到host主机,这样其他人在使用docker inspect
检查镜像时就能够发现这个端口映射提示(hint)。但是,需要注意
:如果没有在docker run
命令显式使用-p 2211:22
,则实际从镜像创建的容器依然是没有输出端口映射的。 请参考 What is the use of EXPOSE in Docker file虽然能够使用
CMD ["/usr/sbin/sshd", "-D"]
在镜像最后启动一个sshd服务,但是在Dockerfile中,只能执行一次CMD
,多条CMD
时后面的命令会覆盖前面的命令(即只有最后一条命令生效);所以如果有多条命令需要运行,则需要使用ENTRYPOINT
。
警告
虽然可以使用 docker build - < ubuntu18.04-ssh
来构建镜像,但是不如使用 docker build -f ubuntu18.04-ssh .
。因为后者可以从当前目录复制文件到容器中(使用 COPY
指令或者 ADD
指令),而前者会失去当前目录引用,复制文件会失败。
备注
如果要在容器内部使用某个用户身份执行命令,则在命令前先使用 USER
指令切换,例如 USER huatai
切换。类似 Python的 virtualenv
环境创建,都需要先切换身份再执行指令。
# Build centos 7 image with ssh:
# ------------------------------
# docker build -f centos7-ssh -t local:centos7-ssh
# create container:
# -----------------
# docker run -itd -p 1122:22 --hostname myserver --name myserver local:centos7-ssh
# create container with volume:
# -----------------------------
# docker volume create data
# docker run -itd -p 1122:22--hostname myserver --name myserver -v data:/data local:centos7-ssh
# create container with volume and static ip:
# -------------------------------------------
# docker volume create data
# docker network create --subnet=172.18.0.0/16 data-net
# docker run -itd -p 1122:22 --hostname myserver --name myserver -v data:/data \
# --net data-net --ip 172.18.0.11 local:centos7-ssh
# create container with volume and static ip, then limit resource and map port:
# -----------------------------------------------------------------------------
# docker volume create data
# docker network create --subnet=172.18.0.0/16 data-net
# docker run -itd -p 1122:22 --hostname myserver --name myserver -v data:/data \
# --net data-net --ip 172.18.0.11 --memory=2048M --cpus="1.5" \
# -p 22 -p 8080:80 local:centos7-ssh
FROM docker.io/centos:7
MAINTAINER vincent huatai <vincent@huatai.me>
RUN yum clean all
RUN yum -y update
RUN yum -y install which sudo openssh-clients openssh-server initscripts
# Prepare sshd host key
RUN ssh-keygen -A
# 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
# 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 service when container started - sshd
EXPOSE 22:1122
#CMD ["/usr/sbin/sshd", "-D"]
# ----------
# WANT run sshd and get a bash
# ENTRYPOINT will not be override by commandline
# ----------
ENTRYPOINT /usr/sbin/sshd && /bin/bash
# Build centos 8 image with ssh:
# ------------------------------
# docker build -f centos8-ssh -t local:centos8-ssh
# create container:
# -----------------
# docker run -itd -p 1122:22 --hostname myserver --name myserver local:centos8-ssh
# create container with volume:
# -----------------------------
# docker volume create data
# docker run -itd -p 1122:22 --hostname myserver --name myserver -v data:/data local:centos8-ssh
# create container with volume and static ip:
# -------------------------------------------
# docker volume create data
# docker network create --subnet=172.18.0.0/16 data-net
# docker run -itd -p 1122:22 --hostname myserver --name myserver -v data:/data \
# --net data-net --ip 172.18.0.252 local:centos8-ssh
# create container with volume and static ip, then limit resource and map port:
# -----------------------------------------------------------------------------
# docker volume create data
# docker network create --subnet=172.18.0.0/16 data-net
# docker run -itd -p 1122:22 --hostname myserver --name myserver -v data:/data \
# --net data-net --ip 172.18.0.252 --memory=2048M --cpus="1.5" \
# -p 22 -p 8080:80 local:centos8-ssh
FROM docker.io/centos:8
MAINTAINER vincent huatai <vincent@huatai.me>
RUN dnf clean all
RUN dnf -y update
RUN dnf -y install which sudo openssh-clients openssh-server initscripts
# Prepare sshd host key
RUN ssh-keygen -A
# 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
# 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 mv /var/run/nologin /var/run/nologin.bak
# run service when container started - sshd
EXPOSE 22:1122
#CMD ["/usr/sbin/sshd", "-D"]
# ----------
# WANT run sshd and get a bash
# ENTRYPOINT will not be override by commandline
# ----------
ENTRYPOINT /usr/sbin/sshd && /bin/bash
警告
2021年12月31日,CentOS 8系列终止更新,所以在 nerdctl 中使用dokcer.io官方镜像已经无法正常更新。解决方法是切换到 CentOS Stream 8
。以下为修订后Dockerfile
# Build centos 8 image with ssh:
# ------------------------------
# docker build -f centos8-ssh -t local:centos8-ssh
# create container:
# -----------------
# docker run -itd -p 1122:22 --hostname myserver --name myserver local:centos8-ssh
# create container with volume:
# -----------------------------
# docker volume create data
# docker run -itd -p 1122:22 --hostname myserver --name myserver -v data:/data local:centos8-ssh
# create container with volume and static ip:
# -------------------------------------------
# docker volume create data
# docker network create --subnet=172.18.0.0/16 data-net
# docker run -itd -p 1122:22 --hostname myserver --name myserver -v data:/data \
# --net data-net --ip 172.18.0.252 local:centos8-ssh
# create container with volume and static ip, then limit resource and map port:
# -----------------------------------------------------------------------------
# docker volume create data
# docker network create --subnet=172.18.0.0/16 data-net
# docker run -itd -p 1122:22 --hostname myserver --name myserver -v data:/data \
# --net data-net --ip 172.18.0.252 --memory=2048M --cpus="1.5" \
# -p 22 -p 8080:80 local:centos8-ssh
FROM centos:latest
MAINTAINER vincent huatai <vincent@huatai.me>
RUN dnf clean all
RUN dnf --disablerepo '*' --enablerepo extras swap centos-linux-repos centos-stream-repos -y
RUN dnf distro-sync -y
RUN dnf -y update
RUN dnf -y install which sudo openssh-clients openssh-server initscripts
# Prepare sshd host key
RUN ssh-keygen -A
# 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
# 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 mv /var/run/nologin /var/run/nologin.bak
# run service when container started - sshd
EXPOSE 22:1122
#CMD ["/usr/sbin/sshd", "-D"]
# ----------
# WANT run sshd and get a bash
# ENTRYPOINT will not be override by commandline
# ----------
ENTRYPOINT /usr/sbin/sshd && /bin/bash
备注
Docker systemd进程管理器 需要比较繁杂的配置(探索),不过现在 CentOS DockerHUB官方镜像 也提供了非常方便的 Docker systemd进程管理器 (详见该文Dockerfile)
alpine linux ssh容器
随着云计算发展, 边缘云计算构建 成为轻量级容器技术的运用场景。使用 Alpine Linux 实现精简的Linux容器可以最大化系统硬件性能发挥,同时降低系统被攻击面。
我在 K3s - 轻量级Kubernetes 部署中采用 Alpine Linux 构建运行容器,采用如下Dockerfile构建支持ssh的容器:
使用密码登陆的ssh容器
Dockerfile
:
FROM alpine:latest
RUN apk add --update --no-cache openssh sudo
RUN apk update && apk upgrade
RUN echo 'PasswordAuthentication yes' >> /etc/ssh/sshd_config
RUN adduser -u 502 -G wheel -h /home/huatai -s /bin/sh -D huatai
RUN echo -n 'huatai:some_password_here' | chpasswd
RUN echo '%wheel ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers
ENTRYPOINT ["/entrypoint.sh"]
EXPOSE 22
COPY entrypoint.sh /
并准备
entrypoint.sh
:
#!/bin/sh
ssh-keygen -A
exec /usr/sbin/sshd -D -e "$@"
然后执行构建镜像命令:
chmod +x -v entrypoint.sh docker build -t alpine-ssh .
使用以下命令启动容器:
docker run -itd --hostname x-dev --name x-dev -p 122:22 alpine-ssh:latest
上述Dockerfile参考 How to install OpenSSH server on Alpine Linux (including Docker) ,原文提供的Dockerfile方法非常精简,我做了一点点修改:
增加sudo,并配置普通用户
huatai
能够免密码sudo
启动容器
既然已经构建成功image,现在可以非常容易就启动容器:
docker run -it -d --hostname ubuntu18 --name ubuntu18 local:ubuntu18.04-ssh
此时使用 docker ps
可以看到新启动的容器:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1a1535ce00ed local:ubuntu18.04-ssh "/bin/sh -c '/usr/sb…" 3 seconds ago Up 2 seconds 22/tcp ubuntu18
启动上述
centos8-ssh
案例命令:docker run -it -d -p 1122:22 --hostname studio --name studio local:centos8-ssh
启动后检查 docker ps
可以看到:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f4928fa6c20d local:centos8-ssh "/bin/sh -c '/usr/sb…" 2 seconds ago Up 2 seconds 1122/tcp, 0.0.0.0:1122->22/tcp studio"'"
登陆
为了方便ssh登陆,可以在 ~/.ssh/config
中添加配置:
Host studio
HostName 127.0.0.1
Port 1122
User huatai
但是 ssh stuido
提示错误:
"System is booting up. Unprivileged users are not permitted to log in yet. Please come back later. For technical details, see pam_nologin(8)."
原因可以通过 man pam_nologin
获得:
pam_nologin是一个PAM模块,当 /var/run/nologin
或 /etc/nologin
文件存在时,就会拒绝用户登陆。这个文件的内容就是显示给被拒绝用户的内容。但是pam_nologin模块对root用户登陆不影响。
果然,我发现CentOS 8的Docker镜像中默认包含了一个 /var/run/nologin
,是用来防范Docker容器使用ssh服务登陆的。只需要移除这个文件就可以。所以,还需要修订Dockerfile自动移除这个文件。