序
docker用linux来理解比较好,万物皆文件。
我们需要用docker来搭建一个environment,无非就是创建一些文件。
当我们使用(attach)这个env的时候,无非是修改各种symbol、pointer,指向这个env对应的文件。
每个env对应一个image file 镜像文件。
使用env时,会从image实例化一个runtime环境对象出来,称这个实例化对象为container。
程序员应该不模式吧。
第一次使用docker环境可以 ab initio
(从零开始),或者用现成的image。
1. 使用现成镜像
一般会推荐新手用现成的image。
但新手会疑问去哪里找image,传送:dockerhub。
docker image采用类似git的方式管理。
常见格式{用户名}/{仓库名}:标签名
。
拉取语法也是git风格
docker pull {用户名}/{仓库名}:标签名
例如
docker pull bitnami/pytorch:latest
标签名(tag)的default value=latest
。
当标签名取缺省值时,可以省略不写。
或者说,当用户省略不写时,docker会自动填入缺省名,搜索latest
这个tag。
有些教程里会把
tag
称为version
,知道这两者等价就行。
前面的{用户名}/{仓库名}
合称为repository name
。也就是说,你可以不写所谓的用户名,docker官方并没有约定这一写法,但我们码农遵循社区礼仪,应该在自己的仓库name前加上自己的用户名标识,以示区分。
每个{repository name}:{version tag}
对应唯一一个image file。
所以上述代码等价于
docker pull bitnami/pytorch
拉取之后直接运行即可。
docker run -it --name pytorch bitnami/pytorch
run的参数
-it
: i表示开STDIN,用于控制台交互;t表示分配tty设备,该可以支持终端登录。
--name
: 实例化container的名字,会显示在docker ps中。
bitnami/pytorch
: 从哪个image启动,后面的tag因为缺省所以自动变成latest。
2.从零开始build
走从零开始这条路呢,直觉上需要“新建env、安装一堆框架、保存到image”。
要麻烦很多。
想新建一个环境,需要自己写一个用于build的Dockerfile。
然后使用build命令
docker build [选项] <上下文路径URL>
这个上下文路径,默认不管的话,输入一个点.
,表示cwd。
根据多年的码农经验,这个文件应该有着类似于cmake之类的构建命令。
docker build 构建过程会在 Docker 后台守护进程 docker daemon 中进行,并不是在客户端工具 CLI 中。
Docker 客户端会将构建命令后面指定的路径(.)下的所有文件打包发送给 Docker 服务端;
Docker 服务端收到客户端发送的包后进行解压,再根据 Dockerfile 里面的指令进行镜像的分层构建;
Dockerfile
是默认约定的文件名。
如果像上面这样直接输入docker build
,会自动搜索cwd下的Dockerfile
文件。
如果想使用的Dockerfile
不在cwd下,就用-f
指定文件。
docker build -f /my/math/to/doooooooockerfile .
有个点 别忘了!
-t
指定输出的repository name
。
docker build -f /my/math/to/dooockerfile -t link/start .
有个点 别忘了!!!
就会创建一个名字叫做"link/start"的仓库名字,由于我们没有写tag,所以docker为我们补上了缺省值latest。
可以使用
docker images
查看本地的仓库,一般第一条就是刚刚build完成的。
Dockerfile语法
全局唯一一条From
FROM hh/bb
ENV 设置环境变量
ENV NUMBER_A=123
RUN 执行cli命令
RUN echo “hello world”
前面有环境变量了,所以可以bash脚本风格取值
RUN echo ${NUMBER_A}
可以用RUN这个装一堆软件
RUN apt-get update
RUN apt-get install -y --no-install-recommends \
包1 \
包2
设置apt、conda、pip代理镜像
RUN sed -i s@/archive.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list && apt-get clean && \
/opt/conda/bin/conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ && \
/opt/conda/bin/conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/ && \
/opt/conda/bin/conda config --set show_channel_urls yes && \
/opt/conda/bin/pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
炼丹师最推荐的手法应该还是走conda路线吧。
RUN conda install -f requirements.txt
最后就是 dockerfile 中的 ENTRYPOINT 和 CMD了,在 docker 容器启动的时候,会自动运行 ENTRYPOINT 和 CMD 的命令,如果 docker run 的时候指定了命令,该命令会作为参数接在 ENTRYPOINT 后,并替换 dockerfile 里 CMD 的命令。
Run
最简单的带cuda的启动方式
docker run -it --gpus all
image_name/image_ID
/bin/bash
-p 端口映射。多个端口需要多个-p
docker run -it -p 8848:8848 -p 8888:8888 -p 9999:9999 -p 9998:9998
文件系统
不太理解。问自己几个问题。
Q:我在container里跑实验,保存数据。之后怎么获取,怎么拿出来?
A: 使用docker cp
docker cp CONTAINER:SRC_PATH DEST_PATH
Q:既然container是一个runtime的实例对象,我如果在container里面存了数据,后面突然断电了,没有经过正规的docker save操作,这些数据会丢失吗?
A: 不会。container里的文件分为runtime和storage两部分。实例化container之后,都存在docker的默认目录下。
一个container被关掉,只会让它的status变成’exited’。
只需要docker restart containerID
即可,就能重新读取storage部分的数据。
当然runtime部分的文件在restart后会丢失。
container需要显式地用命令删除,才能彻底删掉storage部分的数据。
Q:有什么办法让docker读写外部文件?
A:
使用docker run
创建 container 时,用-v
挂载目录。
这样在container内可以读取宿主机上的文件,写入数据也在宿主机上。
挂载目录方法 -v
(Data Volumes 数据卷)
docker run [其他指令…] -v /home/user1/:/dst/path/
-v
指令的本质是一个软链接,所以不限于dir->dir,可以实现file->file。
如果想保持容器运行并退出终端,按 CTRL+P+Q;
重新进入容器的终端,执行 docker attach containerID;
内部存储
关键词叫storage driver
。
一般存储在device mapper
中。
查看docker根目录
$ docker info | grep ‘Root’
Docker Root Dir: /home/admin/docker
/home/admin/docker/overlay2
/home/admin/docker/devicemapper
查看具体container中的数据存储路径。
docker inspect + 容器名/ID
3.容器操作
连接
docker attack {containerName or containerID}
退出、删除
内部退出
exit退出 ,ctrl+d
不关闭地escape, ctrl+p+q
外部关闭
docker stop containerID
docker stop $(docker ps -a -q) 关闭所有容器
删除容器必须在关闭所有容器的前提下。因为不同容器是共享某些资源的(节约空间)。
docker rm containerID
暂停恢复
#暂停容器
docker pause a229eabf1f32
# 取消暂停容器
docker unpause a229eabf1f32
保存修改,提交镜像
commit
docker commit containerID repo_name:tag
阿里云镜像服务器
. 将镜像推送到Registry
$ docker login --username=****@**.com registry.cn-hangzhou.aliyuncs.com
$ docker tag [ImageId] registry.cn-hangzhou.aliyuncs.com/myrepo/myimage:[镜像版本号]
$ docker push registry.cn-hangzhou.aliyuncs.com/myrepo/myimage:[镜像版本号]
4.docker + ssh 配置
docker pull continuumio/anaconda3
docker run --gpus all --name=doc_conda3 -idt -p8001:22 -p8888:8888 continuumio/anaconda3 /bin/bash
结尾/bin/bash表示进去就启动bash。
-p:指定端口映射,格式为:宿主机端口:容器端口。
-p 8001:22 表示用宿主机8001映射容器22
EXPOSE 22
apt-get update
apt-get install openssh-server
apt-get install openssh-client
修改sshd配置
vim /etc/ssh/sshd_config
禁用密码
PasswordAuthentication yes # 默认yes
PubkeyAuthentication yes #启用公钥私钥配对认证方式
#PermitRootLogin prohibit-password #默认展示密码
PermitRootLogin yes #改成yes, 允许root用户使用ssh登录
重启sshd
/etc/init.d/ssh restart
或者
service ssh start
不能用systemctl restart sshd.service
因为默认systemctl是不能在 docker容器中使用的,除非run的时候带上–priviledge=true
生成rsa密码
ssh-keygen -t rsa
Enter file in which to save the key (/root/.ssh/id_rsa):
Created directory ‘/root/.ssh’.
Your identification has been saved in /root/.ssh/id_rsa
Your public key has been saved in /root/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:0KBt9nhyBEHUsCVT4KqhiSdEZI/SQ0r1OaPKEc0NDfg root@0e4402636444
这样生成的两个文件在
~/.ssh/id_rsa.pub
,~/.ssh/id_rsa.pub
其中公钥 ~/.ssh/id_rsa.pub
, 需要放到服务端 ~/.ssh/authorized_keys
,用来解密。
怎么挪过去不重要,重要的是要挪过去。
cp ~/.ssh/id_rsa.pub ~/.ssh/authorized_keys
然后就可以用私钥去登录有公钥的服务器。
docker cp doc_conda3:/root/.ssh/id_rsa ~/.ssh/id_rsa
ssh
-v 显示debug信息
-i 指定私钥文件。
不指定的话,只会找四个默认文件名。四个默认文件都不存在就用password。debug1: Trying private key: /home/USER/.ssh/id_rsa debug1: Trying private key: /home/USER/.ssh/id_dsa debug1: Trying private key: /home/USER/.ssh/id_ecdsa debug1: Trying private key: /home/USER/.ssh/id_ed25519
5.docker端口映射需求
很麻烦啊。
如果docker开启不特定的服务,就会有动态端口映射的需求。
除非你预先把可能遇到的所有端口都写上。
需求有两种解决路径
- 为运行的container 随时动态添加端口映射
- 为运行的container分配一个ip。这样container里面运行新的东西,就能用ip随时访问了。
//参考https://blog.csdn.net/zuoshenglo/article/details/78402772
docker inspect
container_name
| grep IPAddress
本机 8001 到 容器 8000
iptables -t nat -A DOCKER -p tcp --dport 8001 -j DNAT --to-destination 172.17.0.19:8000
端口映射 最佳实践
提前规划好几个常用端口
ssh端口必留。 8001 -> 22。
jupyter lab端口一个。8888 -> 8888。
tensorboard端口一个。 8002->8002。
规划一个公共存储空间,所有代码+数据+中间结果都放这。
/data1/username/
即docker只承载运行环境。 代码数据是外部的,只是借docker使用。
于是可以配置文件里alias写好启动命令
alias dockerrun="docker run --gpus all --name=doc_conda3 -idt -p8001:22 -p8888:8888 -p8002:8002 -v ~/proj:/root/proj -v /data1:/data1 -v /data2:/data2 myrepo/myimage:latest /bin/bash"
还可以加上ssh命令
alias ssh-docker="ssh root@localhost -p 8001 -i ~/.ssh/id_rsa"
除了alias 另一种方法是
https://docs.docker.com/compose/
多次commit合并打包
docker commit的逻辑是, 删除也算增量操作。
所以commit多次,哪怕加一个文件再删一个,最终image大小还是会增加。
怎么把删除操作合并,使得最终镜像减小,就得手动打包。
参考
https://blog.csdn.net/u011195077/article/details/108148824
1.根目录下打包
记得使用--exclude
排除不需要的目录。
# 根目录下:
tar --exclude=/proc --exclude=/sys --exclude=base_img.tar -cvf base_img.tar .
2.复制打包出来的文件
# 退出
exit
# copy
docker cp [容器id]:/base_img.tar .
3.导入新容器
# 导入
cat base_img.tar|docker import - base_img
最后得到的这个base_img
镜像文件,只有一个commit记录。