Docker

常用CLI指令

镜像相关

  • docker pull:下载一个镜像
  • docker build -t :编译一个镜像
  • docker imags:查看所有已有镜像
  • docker image rm :删除一个镜像

容器相关

  • docker run:创建并运行一个容器,处于运行状态,它包含以下常见指令:

    • 基础参数:

      • -d:后台运行容器
      • -it:交互终端运行
      • –name:指定容器名称
      • –rm:容器退出后自动删除
      • -w:设置工作目录
    • 资源限制

    • 网络配置:

      • -p:端口映射
      • –net:网络模式
    • 环境变量:

      • -e / –env: 设置环境变量
    • 用户:

      • -u:指定用户
  • docker pause:让一个运行的容器暂停

  • docker unpause:让一个容器从暂停状态恢复运行

  • docker stop:停止一个运行的容器

  • docker start:让一个停止的容器再次运行

  • docker rm:删除一个容器

  • docker ps:查看当前正在运行的容器

  • docker ps -a:查看所有容器(包含已经停止了的)

  • docker exec:在运行中的容器执行命令,一般用来进入一个创建了的容器,它的参数其实和run命令的差不多

  • ctrl + D:==退出容器==

数据卷相关

  • docker volume create:创建一个数据卷
  • docker volume ls:列出所有数据卷
  • docker volume inspect :查看某个数据卷
  • docker volume rm :删除某个数据卷

为什么要用数据卷?

  • 容器里的数据和镜像是无关的,如果使用镜像创建个新的容器,是无法继承原来的数据的,数据卷相当于在容器里创建个软链接,真正存储的位置是宿主机的磁盘,这样多个容器就可以共享数据了,删掉某个容器数据也不会消失,直到显式删掉数据卷

删除缓存

1
2
3
4
5
docker system df # 查看各部分占用磁盘大小

docker container prune # 删除停止的容器
docker image prune -a # 删除未使用(停止)的镜像
docker builder prune # 删除构建缓存

Dockerfile

如果我们要制作自己的景象,则需要编写dockerfile文件并进行编译,它的常见指令如下:

指令 说明 示例
FROM 指定基础镜像 FROM ubuntu:20.04
RUN 执行命令并创建新的镜像层 RUN apt-get update && apt-get install -y python3
COPY 复制宿主机的文件/目录到镜像中 COPY . /app
ADD 类似COPY,但支持URL和自动解压 ADD https://example.com/file.tar.gz /tmp/
CMD 容器启动时的默认命令(可被docker run覆盖) CMD ["python", "app.py"]
ENTRYPOINT 容器启动时的主要命令(不会被docker run覆盖) ENTRYPOINT ["python"]
ENV 设置环境变量 ENV PYTHONUNBUFFERED=1
ARG 定义构建时的变量(仅在构建过程中有效) ARG VERSION=1.0
WORKDIR 设置镜像的工作目录,相当于在镜像中运行cd,dockerfile中后续指令以及进入容器时都在该目录下 WORKDIR /app
EXPOSE 声明容器运行时监听的端口 EXPOSE 80
VOLUME 创建挂载点 VOLUME /data
USER 指定运行容器时的用户名或UID USER nobody

参考链接:

环境搭建步骤示例

1.拉取docker镜像

1
docker pull dockerpull.org/ubuntu:20.04 
  • 通过这种方式使用镜像站dockerpull.org
  • 下载完成后可以通过docker images查看是否拉取成功

2.创建容器

1
2
3
4
5
6
7
8
# 创建新容器
echo "start a new container : RVOS"

docker run -it -d \
--name RVOS \
-v /home/jiahan/Desktop/RVOS:/workspace \
--net host \
dockerpull.org/ubuntu:20.04
  • --name : 给容器起一个名字,比如叫做risc-v
  • -it: 交互模式(-i)和分配一个终端(-t
  • -d: 后台运行容器
  • --net host:使用主机的网络堆栈,与主机共享网络配置
  • -v:挂载数据卷

3.进入容器

1
2
3
4
5
6
7
8
# 启动容器(关机的话容器就进入stop态了,需要重新启动容器)
docker start RVOS

echo "Enter container: RVOS"
docker exec \
-u root \
-it RVOS \
/bin/bash
  • -u root:以root用户登录,后面就不需要sudo了。如果普通的-u则是非root用户

4.容器换源

Docker内的Ubuntu默认使用的是apt官方的源,下载东西很慢,所以要换国内源

如何更换?

  • 原理:修改/etc/apt/sources.list

可以通过下面的方式实现:

1
2
3
4
sed -i s@/archive.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list
sed -i s@/security.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list
apt-get clean
apt-get update

5.下载依赖

1
2
apt-get update
apt-get install build-essential gcc make perl dkms git gcc-riscv64-unknown-elf gdb-multiarch qemu-system-misc

到此环境就搭建完毕了!后续直接运行enter_container.sh进入容器就可以了

镜像在硬盘中的存储方式

不用担心镜像过多太占磁盘,新镜像只存增量部分,其余部分和父镜像占用一个磁盘空间

Docker 镜像是由多层(layers)组成的,每一条 Dockerfile 指令(如 RUN, COPY, ADD)都会生成一个新层

每一层都被缓存成一个只读的、可复用的文件系统层。Docker 引擎在构建镜像时会尽可能重用已有层来节省磁盘空间

可以用docker history <image>来看某镜像使用了哪些层

遇到的问题

容器内无法使用GPU

  • 需要安装NVIDIA Container Toolkit并重启docker才行
1
2
3
4
5
6
7
8
# 配置软件源
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list

sudo apt update
sudo apt install -y nvidia-container-toolkit
sudo systemctl restart docker

重新启动容器ROS2不见了

  • 每次启动时,需要手动运行ROS2里的setup.sh来添加环境变量
1
2
3
4
5
enter_container:
docker start ros2
docker exec -it \
-u root \
ros2 /bin/bash -c "source /opt/ros/humble/setup.bash && /bin/bash"

容器内无法显示GUI

1.宿主机允许容器访问X11

1
xhost +local:root  # 允许本地 root 用户访问 X11

2.创建启动容器时,设置挂载和环境变量

1
2
3
4
5
6
7
docker run -it \
--env="DISPLAY" \ # 传递显示变量
--env="QT_X11_NO_MITSHM=1" \ # 解决 Qt 共享内存问题
-v /tmp/.X11-unix:/tmp/.X11-unix:rw \ # 挂载 X11 socket
--net host \ # 使用主机网络(简化 X11 通信)
<iamge_name> \
bash

宿主机无法修改容器内创建的文件

  • 引起这个问题的原因是进入容器的时候使用的是root用户,这样容器内创建的文件都是root的,而宿主机默认不是root用户,所以就访问不了

  • 解决办法:进入容器的时候使用和宿主机一样的用户。但是普通镜像并不会包含宿主机用户的账号密码,如果直接在创建容器时设置使用的用户,会出现密码错误的问题。必须重新创建个镜像,添加用户信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
FROM osrf/ros:humble-desktop-full-jammy

# 替换国内源
RUN sed -i s@/archive.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list && \
sed -i s@/security.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list

# 创建用户
ARG UID=1000
ARG GID=1000
RUN apt-get update && \
apt-get install -y sudo && \
groupadd -g $GID jiahan && \
useradd -m -u $UID -g $GID -s /bin/bash jiahan && \
echo "jiahan ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers && \
apt-get clean && rm -rf /var/lib/apt/lists/*

USER jiahan
WORKDIR /home/jiahan

使用docker build --build-arg UID=$(id -u) --build-arg GID=$(id -g) -t my_ros2 .编译上述Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
create_container:
docker run -it \
--name ros2 \
--user $$(id -u):$$(id -g) \ # 指定和宿主机一样的用户
-v /etc/passwd:/etc/passwd:ro \
-v /etc/group:/etc/group:ro \
-v /home/$$(whoami):/home/$$(whoami) \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-v /home/jiahan/Desktop/learn_ros:/workspace \
--net host \
--env="DISPLAY" \
--env="QT_X11_NO_MITSHM=1" \
my_ros2:latest \
/bin/bash

过度共享导致隔离降低

在上面的例子中,创建容器的时候挂载了/home/$$(whoami),这其实导致了过度共享,容器可以访问宿主机的可执行文件和环境,破坏了容器的隔离性

如果宿主机的一些程序(可执行文件)放在~下,那么容器内就可以直接访问了,比如宿主机在~安装的miniconda就直接到容器内了,这会污染容器的python环境

解决办法:

  • Ubuntu下很多安装的软件的路径都会被添加到环境变量里,可以通过修改环境变量来消除某软件的可见性从而避免污染。比如miniconda其实就是在~/.bashrc中被添加的,那么我们在启动bash时,可以通过--norc来不执行它