在 Docker 中使用 Archlinux 的折腾记录

9 min read

写一篇在 macOS 上折腾 Docker 与 Archlinux 的文章,不想看废话可以直接看 Dockerfile

目前我主要使用的操作系统是 macOS 和 Manjaro,并且将会慢慢过度到 macOS 上。但是我的生活学习又有点离不开 Linux。尽管 macOS 也是类 Unit 的操作系统,能够兼容 POSIX,但毕竟不是正宗的 Linux。macOS 也没有像 Windows 一样提供 WSL 这样的 Linux 子系统。所以想要在 macOS 上运行 Linux 不得不使用 VirtualBox 这样臃肿的虚拟机,直到 Docker for Mac 的出现。

Docker for Mac 的本质仍然是虚拟机,但它使用的 Hyperkit 可要比 VirtualBox 轻量多了,而且根据目前的趋势来看,容器化技术将会在未来得到越来越多的应用。而 Docker 又正好是事实上的容器化标准了,尽早在生产学习生活中使用 Docker 有利于更好的理解“容器化的未来”。

话是这么说,但就最终的结果来看,在 Docker 中使用 Archlinux 和在 VirtualBox 中使用 Archlinux,体验上并没有什么本质上的不同,甚至还有诸多限制。把 Docker 当成虚拟机来用也不是“容器化的未来”所提倡的。但也不是不能用。

**并不为了使用 Archlinux 所以用了 Docker,而是因为要使用 Docker 所以顺便用 Docker 解决一下使用 Archlinux 的问题。**如果没有使用 Docker 的打算还是跟推荐用 VirtualBox 等虚拟机装 Archlinux 的方案。

尝试在打包一个可以用 ssh 登录的 Archlinux 容器镜像

我的思路是这样的:先打包一个 Archlinux 的镜像,然后用 Docker 在 后台运行它,并暴露 22 端口。最后在宿主机上用 ssh 登录容器中的 Archlinux。

首先要解决的是镜像的问题,Archlinux 官方提供了一个从 Arch 内核剥离出来的 Docker 容器镜像:archlinux/base

但这个镜像实在是太基础了,像 ssh 这些功能都没有。所以我准备先把 archlinux/base 跑起来,然后用 pacman 装上我们需要的软件,最后再用 docker commit 打包一个新的镜像。

首先拉取 archlinux/base,然后运行它:

docker pull archlinux/base
docker run -it -p 2222:22 --name arch archlinux/base

然后在容器中用 passwd 配置一下 root 的密码:

passwd root

然后用 pacman 安装 opensshsudo

pacman -Syu openssh sudo

然后配置一下 hostkeys:

rm -f /etc/ssh/ssh_*_key
ssh-keygen -q -N "" -t dsa -f /etc/ssh/ssh_host_dsa_key
ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key
ssh-keygen -q -N "" -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key
ssh-keygen -A
sed -i "s/#*UsePrivilegeSeparation.*/UsePrivilegeSeparation no/g" /etc/ssh/sshd_config
sed -i "s/#*UsePAM.*/UsePAM no/g" /etc/ssh/sshd_config
sed -i "s/#*PermitRootLogin.*/PermitRootLogin yes/g" /etc/ssh/sshd_config

最后用 systemd 启动 sshd

systemctl start sshd.server

这时,问题出现了!因为 systemd 的 PID 不是 1,所以无法正常工作。

System has not been booted with systemd as init system (PID 1). Can't operate.
Failed to connect to bus: Host is down

既然如此,那我们将 systemd 作为容器中第一个被启动的应用那么 PID 不就是 1 了吗?

让我们用 docker commit 命令将安装了 openssh 的容器打包成镜像:

docker commit -a "mogeko" -m "mogeko/archlinux" arch

然后使用 docker images 查看新镜像的 IMAGE ID,并用 docker tag 给我们的容器重新起个名:

docker tag <IMAGE ID> mogeko/archlinux

最后删掉原来的容器,并启动新的镜像:

docker stop arch && docker stop arch
docker run -it -p 2222:22 --name arch mogeko/archlinux /lib/systemd/systemd

结果仍然报错:

Failed to mount tmpfs at /run: Operation not permitted
[!!!!!!] Failed to mount API filesystems.
Freezing execution

转念一想,docker 的守护进程不就是容器的 init 吗?在容器中用 systemd,那不是脱裤子放屁吗?

既然我们是想让 sshd 在容器启动时启动,不如直接将 /usr/bin/sshd 作为 PID 1 的任务

让我们删掉原来的容器重新起一个:

docker stop arch && docker stop arch
docker run -it -p 2222:22 --name arch mogeko/archlinux /usr/bin/sshd -D

没有报错就说明启动成功了。

让我们用 Ctrl + P + Q 将容器挂起 (在后台运行),然后在宿主机上用 ssh 进入的到容器中:

ssh -p 2222 root@127.0.0.1

至此,一个可以用 ssh 登录的 Archlinux 容器镜像就算是建好了。

但这还远远不够,还有很多可以优化的地方。

使用 Dockerfile 打包镜像

使用 docker commit 打包出来的镜像虽然实现了需要的功能,但每次启动都需要指定 /usr/bin/sshd -D。实在是很不方便。

所以我编写了一个 Dockerfile 来打包镜像:

FROM archlinux/base
MAINTAINER mogeko
EXPOSE 22
RUN pacman -Syu sudo openssh --noconfirm --needed \
 && pacman -Scc --noconfirm \
 && rm -f /etc/ssh/ssh_*_key \
 && ssh-keygen -q -N "" -t dsa -f /etc/ssh/ssh_host_dsa_key \
 && ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key \
 && ssh-keygen -q -N "" -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key \
 && ssh-keygen -A \
 && sed -i "s/#*UsePrivilegeSeparation.*/UsePrivilegeSeparation no/g" /etc/ssh/sshd_config \
 && sed -i "s/#*UsePAM.*/UsePAM no/g" /etc/ssh/sshd_config \
 && sed -i "s/#*PermitRootLogin.*/PermitRootLogin yes/g" /etc/ssh/sshd_config
CMD ["/usr/bin/sshd", "-D"]

或者你也可以直接 pull 我打包好的镜像:

docker pull mogeko/archlinux

打包好镜像后直接在后台启动这一容器:

docker run -d -p 2222:22 --name arch mogeko/archlinux

然后用 docker exec 进入容器配置 root 密码:

docker exec -it arch bash
passwd root

最后用 ssh 登录:

ssh -p 2222 root@127.0.0.1

新建用户

果然还是不习惯直接使用 root 用户。

在容器中使用一下命令新建用户,并配置用户密码:

useradd -d /home/mogeko -m mogeko
passwd mogeko

然后吧新建好的用户添加到 sudoers

echo 'mogeko ALL=(ALL) ALL' >> /etc/sudoers

退出容器,用新建的用户登录

ssh -p 2222 mogeko@127.0.0.1

挂载宿主机中的文件夹

使用 Docker 还要一个好处就是可以很方便的通过挂载宿主机上的文件夹来共享文件。

我最先想的是直接挂载宿主机整个 /Users 目录到容器的 /home 目录。后来想了想发现这样做很容易把 $HOME 弄乱。而且 macOS 中的 $HOME 中装了 rust 等开发工具,这些应用并不能在 Linux 下运行,如果想要在 Linux 中安装这些软件的话,可能回发生冲突。

最终我还是将宿主机的 $HOME 挂载到容器的 /home/host 目录下,然后再用系统链接将需要共享的文件和文件夹链接到容器的 $HOME 中。

首先,像 DownloadsMusicMoveis 这些文件夹基本上是可以通用的,通通链接过来。

ln -s /home/host/Desktop /home/mogeko/Desktop
ln -s /home/host/Documents /home/mogeko/Documents
ln -s /home/host/Downloads /home/mogeko/Downloads
ln -s /home/host/Library /home/mogeko/Library
ln -s /home/host/Moveis /home/mogeko/Videos
ln -s /home/host/Music /home/mogeko/Music
ln -s /home/host/Pictures /home/mogeko/Pictures
ln -s /home/host/Public /home/mogeko/Pubilc

然后还有一下像 .ssh.bashrc 这样的隐藏文件/文件夹可以按照实际情况配置

ln -s /home/host/.bash_aliases /home/mogeko/.bash_aliases
ln -s /home/host/.bashrc /home/mogeko/.bashrc
ln -s /home/host/.ssh /home/mogeko/.ssh

免密码登录 ssh

每次通过 ssh 登录 Docker 里的 Archlinux 都要输一遍密码太麻烦了,可以通过配置 ssh 实现免密登录。

方法很简单,将宿主机的 ~/.ssh/id_rsa.pub 中的公匙添加到容器的 ~/.ssh/authorized_keys 中即可。

因为我是容器和宿主机共用一个 ~/.ssh,所以我只需要在容器或者宿主机中执行下面这个命令就可以了:

cat '~/.ssh/id_rsa.pub' >> '~/.ssh/authorized_keys'