话题引入
实际需求
博主本人平时使用C++和Python进行开发,主要用到Pytorch进行神经网络的设计和训练、用到Opencv进行图像处理、用到ONNX或者TensorRT进行神经网络推理。除此之外,有时还会用到QT、PCL、ROS等。我希望能达到的一种状态是写的代码在各个平台上都能编译、DEBUG和运行。为实现这个目标,我目前为止尝试过虚拟环境包管理器(miniconda/vcpkg)、虚拟机(VMware/HyperV)、Vtoy虚拟磁盘等方法,这些方法各有千秋,但是都没能达到我的期望。最近,我接触到了容器技术,在使用了一段时间之后,发现它能完美满足我目前的工作需要,于是在此记录一下配置过程。
Docker技术及基础用法
Docker是一种基于操作系统层面的虚拟化技术,其依赖于Linux的系统内核(因此如果需要在Windows系统中使用,需要开启Windows子系统WSL)。Docker与其他虚拟化技术的区别在于,它不需要将整个计算机硬件或整个操作系统虚拟出来。相反,Docker利用操作系统的特性,如Linux内核的命名空间和控制组(cgroups),在宿主机操作系统上创建和管理轻量级的容器。每个容器都是一个独立的运行环境,拥有自己的文件系统、进程空间和网络接口。相对于传统的虚拟化技术,Docker容器的启动速度更快,资源占用更少,并且具有更高的可移植性和可伸缩性。由于容器共享主机操作系统内核,所以它们相对较轻量级,可以在相同的硬件上运行更多的容器。
每个Docker容器都基于Docker镜像。镜像是一个只读的模板,包含了构建和运行容器所需的文件系统、应用程序代码和依赖项。镜像可以根据需要进行部署,可以在本地构建,也可以从Docker Hub等镜像仓库中获取。容器可以根据镜像创建,并通过在容器中修改文件系统或运行特定的命令来定制。
Docker引擎和工作流
使用三张图描述docker技术:
有关docker架构的解释前人之述备已,这里不再过多赘述,提供以下参考链接:
https://cloud.tencent.com/developer/article/1611733?shareByChannel=link
https://blog.csdn.net/m0_51458935/article/details/119576548
操作Docker的基础命令
docker version # 查看docker版本
docker ps # 查看有哪些容器在运行之中
docker images # 列出所有镜像
docker info # 列出docker的信息
docker search nginx # 搜索镜像
docker pull nginx:latest
docker image ls # 查看本地镜像有哪些
docker rmi d1a364dc548c # 根据id号删除镜像
# 运行该nginx镜像,生成容器
docker run 参数 镜像名字/id
docker run -d -p 801:80 nginx # -d: 后台运行 -p 801:80将容器的80端口映射到宿主机的801端口
docker stop 容器id # 停止容器,容器id可通过docker ps查看
docker start 容器名字/id # 开启容器
docker restart 容器名字/id # 重启容器
docker run -it 容器名字/id bash # 进入容器以内,-i:交互操作 -t:新开一个终端, bash:bash环境
exit 退出容器空间
docker exec -it 容器名字/id bash # 进入正在运行的一个docker空间
Docker镜像管理
如果想自定义一个镜像,需要做以下工作:
- 获取基础镜像,选择以恶搞发行版平台(Ubuntu,centos)
- 在环境中安装所需要的润建
- 导出镜像文件
- 获取镜像
- 默认的docker的仓库,dockerhub,有大量优质的镜像
-
docker search 镜像
:标签 搜索镜像 -
docker run -it --rm centos bash --rm
:退出时删除该容器
- 查看镜像
- 删除镜像
-
docker rmi 镜像名/id(id的前三位即可)
删除image的时候不能有依赖的容器 docker rm 容器
删除容器
-
- 镜像管理综合
- 导出镜像:先提交之后再导出镜像,
docker image save centos:7.8.2003 > /opt/111.tgz
- 导入镜像:
docker image load-i /opt/111.tgz
- 查看镜像的详细信息:
docker image inspect 镜像id
- 导出镜像:先提交之后再导出镜像,
Docker容器管理
docker run 镜像名/id
创建并启动容器,如果镜像不在本地,则下载
如果在容器内什么都不做,容器也会自动退出
docker run centos # 运行之后直接退出容器,因为容器内没有执行任务
# 多次运行容器会产生多次容器记录
docker ps -a # 查看所有容器记录
### 针对宿主机:前台运行 ###
docker run -it centos bash # 运行容器且进入容器内,容器不会退出
docker run -it centos ping baidu.com # 运行容器且进入容器内运行一个程序
### 针对宿主机:后台运行 ###
docker run -it -d centos ping baidu.com
docker run -it -d --rm centos ping baidu.com #运行之后删掉容器记录
docker run -it -d --name abc centos ping baidu.com # --name为容器起名
docker log -f 容器id # 查看容器日志,并实时刷新
ps -ef 显示正在运行的进程
docker containter inspect 容器id # 查看容器详细的信息
docker run nginx # 会执行根目录下的docker-entrypoint.sh文件
docker port 容器id # 查看容器的端口转发情况
### 容器的提交 ###
# 在容器内修改完之后,提交,之后再运行
docker commit 容器id 新的镜像名(通常这样写heene/new)
dockerfile
dockerfile主要组成部分:
- 基础镜像信息:
FROM centos:6.8
- 制作镜像时候的操作:
RUN yum install mysql-server
- 容器启动时执行指令:
CMD ["/bin/bash"]
dockerfile命令:
指令 | 功能 | 指令 | 功能 | |
---|---|---|---|---|
FROM |
指定基础镜像 | WORKDIR |
切换工作目录 | |
MAINTAINER |
指定维护者信息,可选 | VOLUME |
设置卷,挂载主机目录 | |
RUN |
运行指定命令 | EXPOSE |
对外暴露的端口 | |
ADD |
拷贝文件到容器内(自动解压),支持URL地址 | CMD |
容器启动之后需要执行的命令 | |
COPY |
拷贝文件到容器内 |
挂载主机上的目录,使用 -v D://1:/home/
参数
额外命令
# dockerfile挂载目录
VOLUME /data # 多个挂载目录使用VOLUME ["dir1","dir2",...],将宿主机上的目录挂载到docker中
# 运行时对外暴露端口
docker port 容器
docker run -p 宿主机端口:容器端口
# 切换用户
USER root
USER heene
使用Tips
docker build --no-cache -t 'heene/test_docker' . -t
:在构建时指定镜像名称。–no-cache:不使用之前的缓存,可以避免一些问题
- 在编写dockerfile的时候,一个RUN会产生一层,为了减少层数,可以将多个步骤合并到一个RUN里面,例如
RUN apt-get update && apt-get install -y \ bzr \ git \ vim && rm -rf /var/lib/apt/lists/*
(最后面,在安装完包之后记得删除缓存) - 为更好的利用缓存,在一些情况下应该添加新的层,比如在后期添加新的软件时可以新开一个RUN
- 使用dive工具可以很方便地查看docker image的内部构成
- 运行中拷贝文件:
docker cp [OPTIONS] LOCAL_SRC_PATH CONTAINER:DEST_PATH
运行docker并打开bash,将docker container的22端口映射到宿主机的8011端口,容器运行结束之后自动清理,挂载E盘的projects文件夹到/root/projects:docker run -it -p 8011:22 --rm -v E:\projects:/root/projects images_name bash
Docker && Cuda && Pytorch && opencv && C++ && Neovim环境配置
我的dockerfile文件内容:
# FROM ubuntu:20.04
## 这里需要注意,11.8以上的cuda版本才支持40系显卡,配了两天11.7😭
FROM nvidia/cuda:11.8.0-cudnn8-devel-ubuntu22.04
WORKDIR /root
ENV DEBIAN_FRONTEND=noninteractive
# 设置环境变量
ENV LANG=zh_CN.UTF-8 \
LANGUAGE=zh_CN:en \
LC_ALL=zh_CN.UTF-8
RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list \
&& sed -i 's/cn.archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list \
&& sed -i 's/security.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list \
&& apt-get -y update \
&& echo "tzdata tzdata/Areas select Asia" | debconf-set-selections \
&& echo "tzdata tzdata/Zones/Asia select Shanghai" | debconf-set-selections \
&& apt-get -y install \
cmake \
libpq-dev \
zip \
tree \
curl \
locales \
build-essential \
clang-format \
openssh-server \
git \
xsel \
ripgrep \
nodejs npm \
llvm \
clang \
screen \
language-pack-zh-hans \
&& apt-get -y autoclean && \
apt-get -y autoremove && \
rm -rf /var/lib/apt/lists/*
RUN git clone https://gitee.com/heren/nerd-fonts.git/ \
&& cd nerd-fonts \
&& chmod +x install.sh \
&& ./install.sh Hack \
&& cd .. \
&& mkdir -p .config/data/debug/ \
&& mkdir -p osc52/bin \
&& mkdir /var/run/sshd \
&& echo 'root:123456bbb' | chpasswd \
&& sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
WORKDIR /root/.config
COPY ./codelldb-x86_64-linux.vsix /root/.config/
COPY nvim-linux64.tar.gz pytorch20.yaml /root/
COPY clipboard-provider /root/osc52/bin
RUN git clone https://gitee.com/fanzhuoxuan/my-nvim-config.git \
&& mv my-nvim-config/* ./ \
&& unzip -q -d data/debug/ codelldb-x86_64-linux.vsix \
&& tar xzf ../nvim-linux64.tar.gz -C ../ \
&& echo export PATH="$HOME/nvim-linux64/bin:$HOME/osc52/bin:$PATH" >> ~/.bashrc \
&& sed -i -e 's/# zh_CN.UTF-8 UTF-8/zh_CN.UTF-8 UTF-8/' /etc/locale.gen \
&& locale-gen && update-locale LANG=zh_CN.UTF-8
## pytorch
WORKDIR /root/
ENV PATH=/root/miniconda/bin:$PATH
COPY ./Miniconda3-latest-Linux-x86_64.sh /root/
RUN chmod +x Miniconda3-latest-Linux-x86_64.sh \
&& bash Miniconda3-latest-Linux-x86_64.sh -b -p $HOME/miniconda \
&& ./miniconda/bin/conda init \
&& rm Miniconda3-latest-Linux-x86_64.sh \
&& apt-get update \
&& apt install libsqlite3-dev
## ffmpeg opencv
RUN apt-get update && apt-get -y install libavcodec-dev libavformat-dev libswscale-dev \
libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev \
libgtk-3-dev libeigen3-dev \
mesa-common-dev libgl1-mesa-dev libglu1-mesa-dev \
libpng-dev libjpeg-dev libopenexr-dev libtiff-dev libwebp-dev \
libxvidcore-dev libx264-dev ffmpeg \
libatlas-base-dev gfortran \
&& git clone https://github.com/opencv/opencv.git \
&& git clone https://github.com/opencv/opencv_contrib.git \
&& cd ~/opencv && mkdir build && cd build \
&& cmake \
-D CMAKE_BUILD_TYPE=RelWithDebInfo \
## 这里会有一个问题,我这里CMAKE_INSTALL_PREFIX明明写的是/usr/local,但是可能安装的时候安装到opencv4里面去了,需要执行以下操作
## 在/usr/local/lib/pkgconfig/opencv4.pc将.../include/opencv4改成.../include
## ln -s /usr/local/include/opencv4 /usr/local/include/opencv2
-D CMAKE_INSTALL_PREFIX=/usr/local \
-D INSTALL_PYTHON_EXAMPLES=OFF \
-D INSTALL_C_EXAMPLES=OFF \
-D BUILD_DOCS=OFF \
-D BUILD_PERF_TESTS=OFF \
-D BUILD_TESTS=OFF \
-D BUILD_PACKAGE=OFF \
-D BUILD_EXAMPLES=OFF \
-D WITH_TBB=ON \
-D ENABLE_FAST_MATH=1 \
-D CUDA_FAST_MATH=1 \
-D WITH_CUDA=ON \
-D WITH_CUBLAS=ON \
-D WITH_CUFFT=ON \
-D WITH_NVCUVID=ON \
-D WITH_IPP=OFF \
-D WITH_V4L=ON \
-D WITH_1394=OFF \
-D WITH_GTK=ON \
-D WITH_QT=OFF \
-D WITH_OPENGL=ON \
-D WITH_EIGEN=ON \
-D WITH_FFMPEG=ON \
-D WITH_GSTREAMER=ON \
-D BUILD_JAVA=OFF \
## 不启用python支持,因为没有进行交叉编译的需要
-D BUILD_opencv_python3=OFF \
-D BUILD_opencv_python2=OFF \
-D BUILD_NEW_PYTHON_SUPPORT=OFF \
-D OPENCV_SKIP_PYTHON_LOADER=OFF \
## 不启用python支持
-D OPENCV_GENERATE_PKGCONFIG=ON \
-D OPENCV_ENABLE_NONFREE=ON \
-D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib/modules \
-D WITH_CUDNN=ON \
-D OPENCV_DNN_CUDA=ON \
## 7.5:10系显卡支持 8.9:40系显卡支持
-D CUDA_ARCH_BIN="7.5,8.9" \
# -D CUDA_ARCH_PTX=8.6 \
## 不启用python支持
# -D CUDA_TOOLKIT_ROOT_DIR=/usr/local/cuda-11.7/ \
# -D PYTHON_DEFAULT_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") \
# -D PYTHON3_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") \
# -D PYTHON3_NUMPY_INCLUDE_DIRS=$(python3 -c "import numpy; print (numpy.get_include())") \
# -D PYTHON3_PACKAGES_PATH=$(python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())") \
.. \
&& make -j"$(nproc)" && make install \
&& mv /usr/local/include/opencv4/opencv2/* /usr/local/include/ \
&& touch /etc/ld.so.conf.d/opencv.conf && echo "/usr/local/lib" >> /etc/ld.so.conf.d/opencv.conf \
&& ldconfig && echo "PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig" >> /etc/bash.bashrc \
&& echo "export PKG_CONFIG_PATH" >> /etc/bash.bashrc && source ~/.bashrc
EXPOSE 22
# 启动ssh
我也将配置好的docker镜像传至DockerHub上,这里也提供相关链接:
https://hub.docker.com/repository/docker/heene/cuda11.8-pytorch-opencv-cpp-nvim/general
使用方法
- 打开Docker Desktop(在Windows环境下,请确保WSL2以开启,并安装Docker Desktop 1.9以上的版本以支持GPU)
- 下载镜像:
docker push heene/cuda11.8-pytorch-opencv-cpp-nvim:tagname
这里的tagname 改成我仓库里面有的标签。 - 启动容器:
docker run -it -p 8011:22 --rm -v E:\projects:/root/projects images_name bash
- 手动打开ssh(原因是在写这篇文字的时候我还没有将启动ssd服务器的命令加到CMD里面):
/usr/sbin/sshd -D
- 使用任意一个终端软件连接上容器,我这里使用的是tabby terminal,它可以选择自动连接上本机的容器,或者你也可以使用ssh连接:
ssh root@localhost -p 8011
密码为123456 - Enjoy! nvim的配置文件在
~/.config/nvim
目录中