Ubuntu镜像(纯粹版本)
之前我在 Ubuntu镜像(采用tini替代systemd) 实践中,一直努力在构建一个能够模拟全功能虚拟机的镜像,也就是在一个容器中同时运行多个服务,特别是 ssh服务 以及 Nginx 这样的基础服务,以便构建一个灵活的开发环境。
不过,有得必有失,过于复杂的容器包装,导致了容器失去了灵活性以及符合 Kubernetes 运行标准的能力,或者说兼容更为繁琐臃肿。所以,大道至简,我现在更倾向于采用标准且轻量的容器,通过编排来灵活组合。
沿用 podman images 实践经验,我现在调整构建 Ubuntu 镜像,来为 容器直接访问AMD GPU 提供基础
base镜像
首先创建一个基础镜像
创建 Dockerfile 为Ubuntu构建基本的系统升级和用户帐号环境:
# Use latest Ubuntu
FROM ubuntu:latest
RUN apt update -y && apt upgrade -y
# Devops utilities
RUN apt -y install sudo openssh-client bind9-dnsutils tmux git vim
# create admin (UID/GID 1000)
ARG USER=admin
ARG GROUP=admin
ARG UID=1000
ARG GID=1000
# Ubuntu images has "ubuntu" account: uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu)
# I change "ubuntu" to "admin" ,maybe you don't need this step
# Ubuntu images has "video" group: gid=44(video)
# I add "render" group: gid=993(render) , for AMD GPU
# and I add admin to group: sudo(27), adm(4), video(44), render(44), admin(1000)
RUN groupmod -n $USER ubuntu
RUN usermod -l $USER -d /home/$USER -m ubuntu
RUN groupadd -g 993 render && \
usermod -aG render $USER
RUN sed -i 's/%sudo ALL=(ALL:ALL) ALL/%sudo ALL=(ALL:ALL) NOPASSWD: ALL/g' /etc/sudoers
USER $USER
COPY vimrc /home/$USER/.vimrc
USER root
CMD ["/bin/bash"]
备注
Ubuntu官方镜像中默认设置了 ubuntu 帐号(uid=1000,gid=1000),我修订为
admin以便和我的集群中Host主机对齐通过对比镜像中组名和组id,我增加了一个
render用于后续AMD GPU所使用 ROCm 所用组对齐调整
/etc/sudoers时需要主机默认配置中列分割符号是TAB而不是空格,所以在执行sed修改时务必复制粘贴正确,否则替换会失败
执行镜像构建:
docker build --rm -t ubuntu-base .
运行容器:
docker run -d \
--name ubuntu-base \
--hostname ubuntu-base \
-p 8080:8080 \
-p 8443:8443 \
-p 9000:9000 \
--user 1000:1000 \
-e LANG=C.UTF-8 \
--workdir /workspace \
-v /home/admin/docs:/workspace \
-v /home/admin/.ssh:/home/admin/.ssh \
ubuntu-base \
sh -c "sleep infinity"
备注
docker run 时使用了参数 --user 1000:1000 : 这个参数可以让容器始终运行在指定 uid/gid 下,对安全有很大提高。
不过,这个参数也带来一个问题: 当使用了 --user 1000:1000 参数之后,Docker默认只加载该UID的主组:
当
docker exec -it ubuntu-base /bin/bash进入容器,可以看到进入时身份就是uid=1000的用户admin,并且执行id命令可以看到该用户只有一个主组,其他在/etc/group中设置的 gid 都不生效:
id 命令输出显示 admin 用户只有主组生效admin@ubuntu-base:~$ id
uid=1000(admin) gid=1000(admin) groups=1000(admin)
正因为
admin只有主组生效,所以即使镜像中已经修订了/etc/sudoers,但是admin的sudo组没有生效,无法使用sudo命令
dev镜像
在开发镜像中,安装 Helix编辑器 以及对应的 Ubuntu环境Helix结合LSP(Language Server Protocol)
# Use latest Ubuntu
FROM ubuntu:latest
RUN apt update -y && apt upgrade -y
# Devops utilities
RUN apt -y install sudo openssh-client bind9-dnsutils tmux git vim
# create admin (UID/GID 1000)
ARG USER=admin
ARG GROUP=admin
ARG UID=1000
ARG GID=1000
# Ubuntu images has "ubuntu" account: uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu)
# I change "ubuntu" to "admin" ,maybe you don't need this step
# Ubuntu images has "video" group: gid=44(video)
# I add "render" group: gid=993(render) , for AMD GPU
# and I add admin to group: sudo(27), adm(4), video(44), render(44), admin(1000)
RUN groupmod -n $USER ubuntu
RUN usermod -l $USER -d /home/$USER -m ubuntu
RUN groupadd -g 993 render && \
usermod -aG render $USER
RUN sed -i 's/%sudo ALL=(ALL:ALL) ALL/%sudo ALL=(ALL:ALL) NOPASSWD: ALL/g' /etc/sudoers
# vim simple config
COPY vimrc /home/$USER/.vimrc
RUN chown $USER:$GROUP /home/$USER/.vimrc
# helix
COPY maveonair-ubuntu-helix-editor-noble.sources /etc/apt/sources.list.d/
RUN apt update && apt install helix -y
# Go
RUN apt-get install -y golang-go
USER $USER
WORKDIR /home/$USER
# install go tools to $HOME/go/bin
RUN go install golang.org/x/tools/gopls@latest && \
go install github.com/nametake/golangci-lint-langserver@latest && \
go install github.com/go-delve/delve/cmd/dlv@latest
# if in CHINA, please use:
# RUN GOPROXY=https://goproxy.cn,direct go install ...
# set PATH include $HOME/go/bin
ENV PATH="${HOME}/go/bin:${PATH}"
# install llm-ls
RUN curl -L https://github.com/huggingface/llm-ls/releases/download/0.5.3/llm-ls-x86_64-unknown-linux-gnu.gz | gzip -d -c > /usr/local/bin/llm-ls && chomd +x /usr/local/bin/llm-ls
CMD ["/bin/bash"]
为了能够解决使用 --user 1000 参数执行 docker run 只加载主组的问题,需要配合 --group-add 参数来为 admin 增加辅助组,以便能够使用 sudo 以及AMD GPU。这种方式在开发环境会非常方便,能够从 admin 用户随时切换到 root 进行一些系统操作:`
docker run -d \
--name ubuntu-base \
--hostname ubuntu-base \
-p 8080:8080 \
-p 8443:8443 \
-p 9000:9000 \
--user 1000:1000 \
# 开发环境允许adm,sudo组用于管理,允许video,render组用于AMD GPU开发
--group-add 4 \
--group-add 27 \
--group-add 44 \
--group-add 993 \
-e LANG=C.UTF-8 \
--workdir /workspace \
-v /home/admin/docs:/workspace \
-v /home/admin/.ssh:/home/admin/.ssh \
# 开发环境允许调试和跟踪
--cap-add SYS_PTRACE \
--security-opt seccomp=unconfined \
ubuntu-base \
sh -c "sleep infinity"
这里 --group-add 分别为 admin 用户添加了组:
4(adm): 在 Ubuntu Linux 中,adm组用于 查看系统日志(Monitoring & Auditing) ,而无需获得root权限。该组成员对/var/log/目录下大部分日志(如syslog,auth.log,kern.log)拥有读取权限,这样admin能够在调试程序时方便查看系统日志
27(sudo): 该组成员通过sudo提升为root用户拥有整个系统超级权限,适合在特定情况下处理系统维护工作
44(video)和993(render)在开发和维护 AMD GPU 时需要该组身份权限
开发环境的系统能力和安全策略
在dev镜像运行时,特别使用了:
--cap-add SYS_PTRACE: 允许容器内部的进程调用ptrace。ptrace是Linux下所有调试工具的核心。例如在容器中使用 strace系统调用跟踪 查看Go程序的系统调用,或者用gdb调试崩溃堆栈,都需要这个权限--security-opt seccomp=unconfined: 将seccomp设置为unconfined(不限制)表示允许容器内的进程能够尝试调用任何内核支持的指令。这对于开发环境、内核调试或性能工具(如 Linux perf性能分析工具 )是必不可少的。如果不设置这个参数,那么即使允许了上文的SYS_PTRACE能力,则依然可能拦截ptrace的某些子功能。(Docker默认会禁止约40多个危险或不常用的系统调用)
警告
在生产环境运行容器绝对不要使用上述两个开发环境的运行参数,存在重大安全隐患
在开发环境中,如果Ubuntu容器中运行 llm-ls 没有反应,可以尝试配置:
--sysctl net.core.somaxconn=1204高并发环境测试监控脚本--shm-size=2g如果涉及到GPU显存和大量内存操作,默认容器的/dev/shm只有64MB可能会导致深度学习框架或高性能内存映射我呢键(mmap)崩溃
ENV
上述 dev 镜像中使用了 ENV 命令:
ENV 指令`# set PATH include $HOME/go/bin
ENV PATH="${HOME}/go/bin:${PATH}"
在Dockerfile中使用 ENV 指令,该配置行 不会添加 到容器内的任何"文件" (如 .bashrc 或 /etc/profile )中,而是直接写入镜像的 元数据(Metadata) 里:
在镜像的JSON配置文件中(通过
docker inspect <image_id>查看)当容器启动时,Docker守护进程( containerd运行时(runtime) )会读取这些元数据,并在创建容器进程(Runtime)时,直接通过系统调用将这些变量注入到进程的环境变量列表(
envp)中
特性 |
Dockerfile ENV |
.bashrc / /etc/environment |
|---|---|---|
存储方式 |
镜像元数据 (Static Metadata) |
磁盘上的文本文件 (File on Disk) |
加载时机 |
PID 1 进程启动前 |
Shell 启动时 (Interactive login) |
覆盖范围 |
所有进程(即便不运行 Shell) |
仅限通过 Shell 启动的进程 |
可见性 |
docker inspect 可见 |
只有进入容器后 cat 文件可见 |
上述使用 ENV 设置镜像环境变量在执行类似 docker exec <container> gopls 命令时,由于没有使用SHELL,会导致 .bashrc 中设置的环境变量失效,但是如果在 ENV 中定义的环境变量则依然有效。