Docker命令及使用方法

写在前面

docker操作必须在root用户或者写入了/etc/group文件的docker组中的用户下才能运行。
将用户添加进docker用户组:sudo usermod -aG docker ${USER}

Docker——从入门到实践

获取镜像

docker search 镜像名 搜索镜像
docker pull 用户名/镜像名 下载镜像
docker image ls 查看下载的镜像列表,只有当镜像出现在这里时才能被接下来的命令调用
参考链接

使用镜像

docker run [各种参数] 镜像名 命令,所有参数都位于镜像名之前,所有要运行的命令都位于镜像名之后

docker run mirror apt-get install ping
docker run mirror ping www.baidu.com  # 无法正确运行,原因不明
docker run mirror /bin/sh -C && ping www.baidu.com  # 可以正确运行,猜测是sh会加载一些环境变量,而ping不在某些环境变量里。我们日常启动一个程序,都是通过shell启动的,而docker run xxx 命令则是直接启动那个命令

镜像是静态的,不会被修改,运行镜像的叫容器,运行结果会保存在容器里面。
每次docker run之后都会将运行结果保存在容器列表中,可通过ps命令查询。
docker ps可以看到当前正在运行的容器
docker ps -a可以看到所有的容器,包括已停止的容器
docker ps -l只显示最新一条结果,感觉不是很好用
每次docker run结束后,docker ps -a的输出就会多出一条
docker ps -s可以看到运行的容器占用的磁盘空间

在实践中,由于通常无法保证代码一次跑通,并且为了更贴近平时无docker的工作状态,在交互模式下运行docker是一个更好的选择:

docker run mirror -it /bin/bash  # -i表示交互模式,-t表示为容器重新分配一个伪输入终端,两者缺一不可

然后其他操作就跟在无docker情形下一模一样了。

由于是虚拟机,一进来就是root,apt-get都不需要加sudo了,美滋滋。但其实现在的root只是外部的一个普通用户权限,要想拥有真正的root权限,需要再加上一个--privileged参数,这时启动的容器可以看到很多host上的设备,并且可以执行mountsshfs参考链接)。
现在还没有网络,apt-get会发现连不上外网,所以run的时候要多加一个参数:

docker run mirror --network host

-w directory 参数用于指定工作目录,相当于一进虚拟机就执行了一个cd directory参考链接
-v dirout:dirin 参数用于将外部目录映射到内部,使得在容器内部可以访问外面的文件和数据
--name name 参数用于给容器起名,不起名的话会随机分配一个“形容词_人名”
--shm-size=512m 参数给容器分配了512M的可用内存,默认64M,如果要跑深度学习实验,64M肯定是不够的,最好是能分配2到4个G
--pid=host 容器内使用与宿主机相同的进程id空间,从而可以看到和处理容器之外的进程

运行时调试容器

容器在启动之后,如果需要限制它的内存占用,防止把服务器搞垮,可以使用docker update命令:
docker update --cpus=20 --memory=40g <container_name>,表示容器最多只能使用40G的内存和20个线程核(对应于top输出里的2000%)

docker stats <container_name>可以查看容器当前的运行状态,包括cpu占用,内存占用等:

CONTAINER ID   NAME  CPU %     MEM USAGE / LIMIT   MEM %     NET I/O   BLOCK I/O        PIDS
a1234567413   xxxx   3.68%     1.267GiB / 80GiB    1.58%     0B / 0B   162GB / 22.9GB   403
关闭与重新启动容器

Ctrl+p再Ctrl+q可以从容器退回宿主机(宿主机就是我们原来的机子),此时容器依旧在运行(也即docker ps能看到它)。如果不是Ctrl+p再Ctrl+q,而是直接Ctrl+D或者exit,容器将停止运行,但不会被删除,下次可以重新启动。若要返回仍在运行的容器,使用docker attach 容器id或容器名字,id或名字在docker ps中查看。若要重新启动已关闭的容器,使用docker start 容器id或容器名字docker exec -it 容器id或容器名字 bash 可以进入正在运行的容器进行调试或查看,适用于查看正在运行服务,不宜频繁中断的容器的状况。对于配置了用户组的容器,如果以普通用户docker run -u <username>启动了容器,后续因为某种权限问题导致进不了容器(一般是VSCode才会,命令行不会出这种问题),要江湖救急的时候就可以通过docker exec --user root <container_id>以root用户进入已启动的容器。

删除容器

由于每次运行结束都会保存运行结果的容器,这将导致docker ps的结果迅速变长。为了更方便地查看结果,要么docker rm 容器id或容器名字手动清理,要么在docker run的时候加上-rm变量,使容器在运行结束后自动删除。
类似地,docker rmi 镜像id删除镜像,镜像id在docker images中查看。'镜像仓库名’作为参数暂未研究。
根据名称批量删除容器或镜像(根据名称查询容器/镜像并停止/删除):

docker rm `docker ps -a| grep repository:tag | awk '{print $1}' `

一个版本的镜像构建完成后,批量删除构建期间因各种原因产生的标签为<none>的中间镜像(参考链接):

docker images | grep none | awk '{print $3}' | xargs -i docker rmi {}

docker ps --filter参数讲解
删除已退出且退出时间超过1周的容器(默认超过一个月的容器已事先被删除):

# docker ps的输出信息空格较多,不能直接按空格来,这里取巧用)分成两段
docker ps -a --filter status=exited | awk -F\) '$2 ~/weeks/ {print $0}' | awk '{print $1}' | xargs -i docker rm {}

docker stop容器时一直卡死的解决办法

# 查看占用该容器的进程
ps aux | grep <container_id>
# 强行杀掉这个进程
kill -9 <pid>
# 再重新stop容器就可以了
docker stop <container_id>
设置入口点

当我们在Dockerfile中定义了ENTRYPOINT之后,如果镜像有问题,在运行的时候报错后容器就会关闭,难以调试,我们可以重写入口点,用普通的/bin/bash访问容器:

docker run -it --entrypoint /bin/bash mirror

参考链接

保存自定义镜像

当我们在容器中做了改动之后,想要保存这个改动,而不是下次又从镜像重新开始做,我们可以用如下的命令将容器保存为镜像:

docker commit -m="描述信息" -a="作者" 容器id 保存的镜像名

如果我们想把一个镜像的某个层当做另一个镜像来用,可以使用镜像重命名命令(参考链接):

docker tag <image_id> <repository>:<tag>

参考链接
docker diff 容器id可以看到容器自启动后发生的文件更改,在这里可以清晰看到多了哪些东西,以便进行清理,减小commit下来的镜像大小。
docker build --squash命令也可以减小编译出的镜像大小,参考链接
docker inspect 镜像id或容器id可以查看镜像或容器的详细信息:

# 查看分配给该容器的共享内存
docker inspect <container_id> | grep -i shm

docker inspect在检查运行容器的状态时很有用,比如说一个运行中的容器,有一个挂载的目录有问题,但是忘记挂载到哪里了,docker ps -a --no-trunc也没有显示,这时就可以用使用docker inspect,查看"Mounts"键值里的"Source"和“Destination”项即可。

另外一种生成镜像的方式是使用dockerfile将要做的改动都写在这里面,然后运行build命令就可以得到新的镜像,感觉跟makefile有点像。

dockerfile写法参考链接
一个例子:

FROM nginx  # 以哪一个镜像作为基础,相当于docker run -it nginx /bin/bash
MAINTAINER foo  # 指定镜像的作者,非必需项
RUN apt-get update  # RUN命令是在镜像中执行的指令
RUN apt-get install sshfs -y  # 非交互式的apt-get都要加-y参数,以略过Y/N环节

每一条指令都会在镜像上生成一个新的层,为了减少不必要的层,有些指令可以连接成一行的尽量连接:

RUN apt-get update && apt-get install sshfs -y

关于镜像的多层结构,分层可以让docker只保存我们添加和修改的部分内容,就像git的机制一样(参考链接
docker history 镜像id可以查看某一个镜像版本之前做了什么,有点像git log或者是git reflog
写完Dockerfile之后就可以进行build了:

docker build -t name[:tag] dir_context

其中-t name:tag给生成的镜像指定了名字跟标签,名字一般就是我们FROM的名字了,tag是每个镜像独有的标签,方便辨别与使用不同版本的镜像。不加-t name:tag生成的镜像不会显示在docker images的结果中(好像是在docker images -a里头)。dir_context是上下文目录,该目录下需要包含Dockerfile文件,其他需要在容器中使用到的外部文件也放在这里,该目录下的所有文件都会被打包传入容器。因为内外文件系统是隔离的,如果想要在容器内部使用外部的某些文件,只能通过这种方式传进去,一般情况下dir_context的值就是.了。传进去之后也不能直接访问,还需要通过COPY等指令,把打包的文件拷贝到容器的文件系统中,然后才能正常访问。COPYADD等我应该会很少用,就先不管了,参考链接放这
build如果要pip install东西,也是需要网络的,所以基本都是要配个网络,跟docker run是一样的:docker build --network host ...
如果需要配置一些仅在编译阶段生效的环境变量,可以通过--build-arg参数传入:

docker build --build-arg HTTP_PROXY=xxx ...

有时候我们还会想把这个镜像打包,拷到其他服务器上去,但是并不想push到公开的仓库,这时就要用到saveload命令了:

# docker save -o 输出路径和文件名 镜像仓库名:标签
docker save -o my_ubuntu_v3.tar runoob/ubuntu:v3

拷到另一台机子之后再:

docker load -i my_ubuntu_v3.tar

就可以用上镜像啦,是不是很方便!

我们还可以把一个容器打包,比方说某个镜像出了问题我们需要重新拉取,但是有一个重要的容器依赖到这个镜像,重新拉取之后需要较复杂的步骤才能恢复这个容器的状态,那我们可以把这个容器直接打包成镜像,这个功能以后就直接从这个镜像启动,然后把有问题的镜像正常移除并重新拉取。

# 将已有的容器导出成tar包
docker export -o <output_file_name>.tar <container_id/container_name>
# 将已有的tar包导入成镜像
docker import <input_file_name>.tar <image_name>
数据卷

有时候我们会遇到需要在不同的设备上部署docker服务的情况,这时需要将数据卷从一台设备迁移到另一台设备。
需要注意几点:

  • docker的数据卷根目录位于docker根目录下的volumes/子目录,docker根目录通过docker info | grep Root查看;
  • 因为docker-compose会自动进行数据卷的创建,并将数据卷的信息记录在数据库中(可以通过docker volume inspect <volume_name>查看),因此直接将数据卷拷贝到volumes/目录下是无法被docker服务识别的;

因此,迁移数据卷可以这么做:

  1. 数据卷一般是docker-compose时自动创建的,首先在新设备上运行docker-compose up -d,令其创建新数据卷,此时docker volume list可以查看数据卷列表;
  2. 在root用户下进入到docker的数据卷根目录,将原数据卷子目录移除,把原设备上的对应数据卷子目录拷过来;
  3. 拷贝完成之后检查数据卷内各目录的uid跟gid是否跟原始的一致,若不一致需要用chown调整属主,否则容器可能会因为权限原因跑不起来。
辅助工具

PS:下面的操作均在容器中进行。
sshfs包可以建立远程目录映射,不占用本地存储空间,使用文件时通过网络从远程服务器的目录上下载。这样做的好处是借用别人的服务器跑实验的时候不需要另外拷一份数据集过来,太占用空间了
安装:sudo apt-get install sshfs
接着在容器的/mnt目录下建立一个目录作为挂载目录,如/mnt/data
然后进行远程挂载:sshfs lab-xxx@xx.xx.xx.xx:/data /mnt/data
然后就可以像在自己的机子上一样调用数据了。sshfs可读可写,所以输出的log也可以直接输出回远程目录。
记得docker run的时候要加--privilege参数,不然挂载的时候会报fuse: device not found, try ‘modprobe fuse’ first错误
参考链接
在调用数据的时候用ifstat查看网络下载速度,可以观察sshfs运行的性能。

其他

为了节省空间,集群后台会定期删除没有使用的镜像,所以docker pull下来要尽快run起来,不要删除容器,否则镜像可能会被删除
Docker时区调整方案
修改镜像默认保存路径,最后重启服务的命令换成sudo service docker start
重启docker服务无响应的解决方法
docker构建不同架构(x86, ARM)镜像
显卡驱动跟cuda是两回事,cuda是编译库,一台机子只有一种显卡驱动,但它可以跟不同的cuda版本配合进行编译及使用,同样代码不同cuda库编译出来的结果可能略有不同,一台机子上可以存放多个cuda版本,名为cuda101pytorch160的镜像自带101版本,在启动时使用这个版本,cuda102的镜像自带102版本,以此类推,所以宿主机的cuda与容器的cuda互不干涉,都能正常使用。
容器内部访问宿主机上的usb,并支持热插拔不过自己实验了貌似不热插拔的话,/dev/bus/usb目录不需要-v就会自动映射进去,就像虚拟机不需要设置就能接收到U盘一样。
Centos安装docker-compose
docker-compose的简单使用
关于Sending build context to Docker daemon数据很大的问题
在容器中使用图形化界面GUI
apt update报错Couldn’t create temporary file /tmp/apt.conf.xx for passing config to apt-key的解决方案
nvidia docker 容器中devel runtime base三种文件的区别:runtime应该足够支持pytorch的使用
在dockerfile中尽量使用apt-get,不使用apt,因为apt会弹交互界面,在脚本中使用会有潜在的问题,参考链接
CUDA Toolkit版本与显卡驱动版本的对应关系表,链接中的表3。
安装显卡驱动

关于配置文件/etc/docker/daemon.json
这个文件是docker的配置文件,很多设置都是通过对这个文件的修改来进行调节,需注意修改后需要重启docker服务(sudo systemctl restart docker)才能应用,而这一操作会中止当前正在运行的容器
让docker build时使用cuda runtime:有这一需求是因为在dockerfile中编译某些so文件时需要用到cuda runtime,否则编译出来的文件在import时会报undefined symbol。需要修改docker的配置文件,添加如下内容(参考链接):

{
    "runtimes": {
        "nvidia": {
            "path": "/usr/bin/nvidia-container-runtime",
            "runtimeArgs": []
         }
    },
    "default-runtime": "nvidia",
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值