基本上就是按照官方的教程一步步来的:https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository
一、基本安装
1、更新一下apt工具包
sudo apt-get update
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg \
lsb-release
2、添加docker官方密钥
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
会出来一个warning,不用管:
gpg: WARNING: unsafe ownership on homedir '/home/lz/.gnupg'
3、配置稳定版仓库(这里需要根据系统架构选,不过大部分主机都是x86_64/amd64的)
echo \
"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
4、然后再更新一下apt工具包,安装最新版本的Docker Engine and containerd(也可以安装指定版本,这里就不提了)
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io
5、可以查看一下docker版本(注意现在需要sudo获取root权限的):
sudo docker version
返回这样一段信息:
Client: Docker Engine - Community
Version: 20.10.5
API version: 1.41
Go version: go1.13.15
Git commit: 55c4c88
Built: Tue Mar 2 20:18:05 2021
OS/Arch: linux/amd64
Context: default
Experimental: true
Server: Docker Engine - Community
Engine:
Version: 20.10.5
API version: 1.41 (minimum version 1.12)
Go version: go1.13.15
Git commit: 363e9a8
Built: Tue Mar 2 20:16:00 2021
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.4.4
GitCommit: 05f951a3781f4f2c1911b05e61c160e9c30eaa8e
runc:
Version: 1.0.0-rc93
GitCommit: 12644e614e25b05da6fd08a38ffa0cfe1903fdec
docker-init:
Version: 0.19.0
GitCommit: de40ad0
二、后安装
1、为了每次运行不用都加sudo
,可以添加用户组:
sudo groupadd docker
返回:groupadd: group 'docker' already exists
2、添加你要给予权限的用户,$USER要换成你自己的,比如我的是lz
:
sudo usermod -aG docker $USER
3、运行下面语句表示退出重新又进一下:
newgrp docker
然后你运行docker version
,发现现在不用加sudo
也可以像上面那样返回版本信息了。也可以用下面的测试一下;
docker run hello-world
返回如下,就表示docker-ce安装成功了:
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
b8dfde127a29: Pull complete
Digest: sha256:308866a43596e83578c7dfa15e27a73011bdd402185a84c5cd7f32a88b501a24
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
更多后安装操作可见(如设置开机自启动,http代理等):https://docs.docker.com/engine/install/linux-postinstall/
三、常见基本命令
首先可以用帮助查看一下命令:
docker --help
然后在这里放一下常用的命令:
docker pull ${CONTAINER NAME} #拉取镜像
docker images #查看本地所有镜像
docker ps #查看所有正在运行的容器,加-q返回id
docker ps -a #查看所有容器,加-q返回id
docker rmi ${IMAGE NAME/ID} #删除镜像
docker rm ${CONTAINER NAME/ID} #删除容器
docker save ${IMAGE NAME} > ${FILE NAME}.tar #将镜像保存成文件
docker load < ${FILE NAME}.tar #从文件加载镜像
docker start ${CONTAINER NAME/ID} #运行一个以前运行过的容器
docker stop ${CONTAINER NAME/ID} #停止一个正在运行的容器
docker logs ${CONTAINER NAME/ID} #显示运行容器的日志
docker run... #运行一个容器
--name ${container name} #设置容器名称
-p ${host port}:${container port} #映射主机和容器内的端口
-e ${env name}=${env value} #添加环境变量
-d #后台运行
-v ${host folder path}:${container folder path} #将主机目录挂在到容器内
# 退出正在运行的容器,并且停掉当前的容器:
exit
# 只是退出,但是容器还在后台运行着,状态为Up
ctrl+p+q
# 重新进入容器
# 如果之前是exit,那就得先start再exec或者attach
docker exec -it ${CONTAINER NAME/ID} bash
docker ps -f "status=exited" #显示所有退出的容器
docker ps -a -q #显示所有容器id
docker ps -f "status=exited" -q #显示所有退出容器的id
docker restart $(docker ps -q) #重启所有正在运行的容器
docker stop $(docker ps -a -q) #停止所有容器
docker rm $(docker ps -a -q) #删除所有容器
docker rm $(docker ps -f "status=exited" -q) #删除所有退出的容器
docker rm $(docker stop $(docker ps -a -q)) #停止并删除所有容器
docker start $(docker ps -a -q) #启动所有容器
docker rmi $(docker images -a -q) #删除所有镜像
docker exec -it ${CONTAINER NAME/ID} /bin/bash #进入容器内
docker exec -it ${CONTAINER NAME/ID} ping ${CONTAINER NAME/ID} #一个容器ping另外一个容器
docker top ${CONTAINER NAME/ID} #显示一个容器的top信息
docker stats #显示容器统计信息(正在运行)
docker stats -a #显示所有容器的统计信息(包括没有运行的)
docker stats -a --no-stream #显示所有容器的统计信息(包括没有运行的) ,只显示一次
docker stats --no-stream | sort -k8 -h #统计容器信息并以使用流量作为倒序
docker system
docker system df #显示硬盘占用
docker system events #显示容器的实时事件
docker system info #显示系统信息
docker system prune #清理文件
以下这篇知乎回答也可参考
后续有打比赛或者用docker运行项目的时候再来更新~
其实最主要还是要学会Dockerfile的书写
四、安装nvidia-docker(或叫NVIDIA Container Toolkit)
对于深度学习方向的小伙伴,肯定是要配置能使用GPU的环境了,首先显卡驱动肯定是要装的,其次上面的docker引擎得装好,然后就可以装nvidia-docker了,官方教程:https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html#setting-up-nvidia-container-toolkit
distribution=$(. /etc/os-release;echo $ID$VERSION_ID) \
&& curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - \
&& curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
sudo apt-get update
sudo apt-get install -y nvidia-docker2 # 这一步网上很多说的是 sudo apt-get install -y nvidia-container-toolkit,我用的后者
sudo systemctl restart docker
# 拉取一个镜像测试一下
sudo docker run --rm --gpus all nvidia/cuda:11.0-base nvidia-smi
成功的话可以看到类似的输出:
PS: 我之前在CUDA10.1的情况下安装过一次NVIDIA Container Toolkit,后来因为项目需要把CUDA变成了CUDA10.2,会出现
docker: Error response from daemon: could not select device driver "" with capabilities: [[gpu]].
这样的报错,后来重新安装一遍NVIDIA Container Toolkit就可以了,也可以看这篇博客
五、拉取一个镜像玩一玩
去nvidia/cuda下拉取一个合适的镜像, 拉取命令:
docker pull nvidia/cuda:10.2-cudnn7-devel-ubuntu18.04
这个tag告诉我们这是一个ubuntu系统,里面预装了cuda10.2和cudnn7,所以剩下的就是装pytorch了,当然也可以直接pull pytorch的镜像, 具体的版本都是根据你自己的需要来:
docker pull pytorch/pytorch:1.5.1-cuda10.1-cudnn7-devel
网上许多教程有设置镜像站点来提高pull的速度的, 但是我没感觉下载镜像比较慢,反而是push比较慢(push慢解决方法请看第八节):
vim /etc/docker/daemon.json
{ "registry-mirrors": ["https://registry.docker-cn.com"] }
- 然后重启进程
systemctl daemon-reload && systemctl restart docker
,这句话只要关于docker的配置改了都最好运行一下
可以用docker images
看一下主机上现在都有哪些镜像:
可以看到我们刚刚拉取的两个镜像来自docker hub上的nvidia/cuda和pytorch/pytorch仓库,各自也有标签tag,镜像ID,还有镜像占用硬盘大小,因为pytorch的镜像安装了pytorch和其他依赖,所以会大一点
六、运行容器,配置自定义环境
当然你下载的要是pytorch/pytorch的镜像的话基本就不用配置环境了(对于pytorch框架使用者来说),如果拉取的是nvidia/cuda可以运行镜像进入容器里面配置自己想要的环境:
docker run --name suibian -v /home/lz/Downloads:/home --gpus all -it nvidia/cuda:10.2-cudnn7-devel-ubuntu18.04 bash
--name
给将要运行的container取一个随便任意一个名字-v
将主机的目录挂载到容器的目录,用分号隔开--gpus
用那几个显卡,all表示机器上的全部-it
是两个参数:-i和-t。前者表示打开并保持stdout,后者表示分配一个终端(pseudo-tty)。此时如果使用exit退出,则容器的状态处于Exit,而不是后台运行。如果想让容器一直运行,而不是停止,可以使用快捷键 ctrl+p ctrl+q 退出,此时容器的状态为Upbash
进入shell界面,一般都是这样运行的,除非后台-d
运行
进去以后就变成root用户,就是一个简易的ubuntu系统,可以装python3或者miniconda啥的,就跟新安装好的ubuntu系统差不多,可能会少些东西
# 先更新一下
apt-get update
apt-get install python3
apt-get install python3-pip
# 或者你想装miniconda,就别装anaconda了
bash Miniconda3-py37_4.10.3-Linux-x86_64.sh
这里放几个常见的情景:
- docker进去pip安装包速度很慢,这时我们需要和物理机上的操作一样,换pip源,具体可参考这篇博客
- 在docker容器中安装opencv-python直接安装后
import cv2
会出现ImportError: libGL.so.1: cannot open shared object file: No such file or directory
这样的报错,具体解决方案可参考这篇博客
注意,这里的挂载就像硬盘挂载一样的,当你把一个目录挂载进容器里,主机修改这个目录下的文件,容器里面也会修改,容器里面修改文件,主机上的文件也就会被修改,所以不要为了图一致方便直接把主机/home挂载到容器的/home下,这样容易不小心把主机的/home下的东西变了。
七、push一个镜像到远程仓库
这时候如果感觉环境配好了,就可以把容器打包成镜像,然后推送到远程仓库了。这里不妨先到docker hub先注册一个账号,然后创建一个仓库 create repository,假设这里的用户名和仓库名叫做remote-username和reponame
打包的命令其实用的是commit,这些步骤都非常像git:
docker commit -a "lz" -m "for running codes" 4f39b4699ffe remote-username/reponame:tagname
-a
Author 备注作者信息-m
备注提交信息4f39b4699ffe
container IDremote-username/reponame:tagname
这已经按照push的格式要求命名本地的镜像名称/仓库名:标签名在,这样命名的好处是在push前不需要重新tag一遍
这时只需要登录并且push,如果之前没有登陆过的,可以直接docker login,如果不能保证可以先docker logout(这里默认有sudo权限了)
docker login
Username: *** # 输入自己注册时的用户名和密码
Password:
# 登陆成功后可以push了
# 因为之前命名已经符合远程push的名字要求了,可以直接push:
docker push remote-username/reponame:tagname
一般情况下在commit
的时候最后面的命名是local-image:local-tagname
,还需要在push之前tag一下:
docker tag local-image:local-tagname remote-username/reponame:tagname
docker push remote-username/reponame:tagname
这里相当于复制了一个内容相同只是tag不同的镜像。这时候你用docker images
看多出来一个镜像,本来是local-image:local-tagname的,现在多出来一个remote-username/reponame:tagname,但是并没有实际占用硬盘空间,因为他们的IMAGE ID是一样的。
push完事之后,刷新docker hub的网页,发现自己的remote-username/reponame下面已经有tagname的镜像了
或者在本地直接保存整个镜像 :
# 后面就是你要保存的镜像
docker save -o save_name.tar username/reponame:tagname
# 要用的时候直接
docker load -i save_name.tar
有时自己搭建的私有库没有https证书或者到期了(不太确定是不是这样哈,暂时这样理解吧),需要在/etc/docker/daemon.json
里面添加 “insecure-registries”: [“内网registry ip[:端口]”], 这里如果有多个ip想要设置的话,得像下面这样写:
{
"insecure-registries": ["内网registry ip[:端口]", "内网registry ip[:端口]"]
}
然后重启一下生效(如果填写端口号了,那么docker login的时候也得带端口号才能成功登录)
systemctl daemon-reload && systemctl restart docker
八、提高docker pull/push的速度
更新一下,速度不感觉慢的话不建议往下弄了,实际发现没多大用
前替你有一个袋里,这个搭建不说了。这样弄完pull和push都会走袋里了
- 创建目录
sudo mkdir -p /etc/systemd/system/docker.service.d
- 创建文件
sudo vim /etc/systemd/system/docker.service.d/http-proxy.conf
- 写入配置
[Service] Environment="HTTP_PROXY=http://127.0.0.1:1080/" Environment="HTTPS_PROXY=https://127.0.0.1:1080/" Environment="NO_PROXY=localhost,.docker.io,.docker.com,.daocloud.io"
- 刷新更改
systemctl daemon-reload && systemctl restart docker
- 验证是否生效
systemctl show --property=Environment docker
Environment=HTTP_PROXY=http://127.0.0.1:1080/
Environment=HTTPS_PROXY=http://127.0.0.1:1080/
九、将容器内的jupyter notebook/lab映射到本地
有时服务器端没有图形化界面,所以用jupyter notebook/lab调试是一个不错的选项,其实也很简单,只要两步就可以:
- 在docker run启动容器的时候要带一个
-p ${host port}:${container port}
参数,比如-p 7777:8888(8888这个默认不要改比较好,前面是你自己本地随意一个端口) - 在容器里面装好jupyter notebook/lab之后就可以在终端运行以下的命令:
这里的8888就是docker run后面的container port,这样到时才能访问。然后在本地浏览器里输入jupyter lab --ip 0.0.0.0 --no-browser --allow-root --port 8888
localhost:7777
就会有jupyter界面了,这里的7777就是docker run后面的host port。
十、如何在新的镜像里面安装ssh服务并开启呢?(update 2024/03/14)
这次想在集群上创建一个新的Docker镜像,就去DockerHub上拉取了一个docker pull pytorch/pytorch:2.1.2-cuda11.8-cudnn8-devel
。但是因为里面没有ssh服务端,所以得在这个ubuntu20.04的镜像里面安装,因为进去就是root了,所以sudo都不用写。
# 在容器内部,运行以下命令来查看 Ubuntu 系统的版本信息:
lsb_release -a
# 安装ssh服务端
apt-get update && apt-get install -y openssh-server
但是你会发现这时候/etc/ssh/sshd_config
和正常本机上不一样,因为它还没运行,当你按照网上去重启ssh服务的时候,发现这样的报错:
System has not been booted with systemd as init system (PID 1). Can’t operate.
Failed to connect to bus: Host is down
一般的docker镜像为了节省空间,通常是没有安装systemd或者sysvint这类初始化系统的进程。通过ps -p 1 显示的 CMD 是 tini。Tini(Tiny-Init)是一个轻量级的 init 进程,专门为容器而设计。它提供了对容器生命周期的管理和信号处理。在许多容器镜像中,尤其是基于 Alpine Linux 的镜像,Tini 是常见的替代 init 进程,仍然是容器中的 PID 1 进程,负责管理其他进程和处理信号。
所以类似systemctl
开头的命令都是不好用的了,例如systemctl restart sshd
,systemctl status ssh
,必须替换为service
:
service ssh status
service ssh start
service ssh restart
当你重启ssh服务后就会发现/etc/ssh/sshd_config
里面内容多了很多了,和本地差不多了,但是最后最关键的一步还不能忘记就是允许root用户登录镜像,因为你每次集群使用都是root用户进去的:
nano /etc/ssh/sshd_config # 没有nano的去装一下: apt install nano
# 在最后一行添加一句
PermitRootLogin yes
# 然后按Ctrl + O即可保存文件,按Ctrl + X即可退出Nano
# 然后重启一下ssh服务:
service ssh restart
这样你就会发现ssh本地可以ssh连接镜像并且进去容器里面了!
十、常见注意事项
- 在基础docker container里面配置环境的时候,要装
opencv-python-headless
,不要像平常时候装opencv-python
- 当碰到
ERROR: Unexpected bus error encountered in worker. This might be caused by insufficient shared memory (shm).
这样报错的时候,需要减小dataloader的num_workers或者在一开始docker run的时候加这样的命令行参数:--ipc=host