Docker实践


Posted on Aug.1, 2020


Docker是个划时代的开源项目,它彻底释放了计算虚拟化的威力,极大提高了应用的维护效率, 降低了云计算应用开发的成本!使用 Docker,可以让应用的部署、测试和分发都变得前所未有的高效和轻松。 无论是应用开发者、运维人员、还是其他信息技术从业人员,都有必要认识和掌握 Docker,节约有限的生命。

简介

  • 什么是Docker
  •       Docker 使用 Google 公司推出的 Go 语言 进行开发实现,基于 Linux 内核的 cgroup,namespace,以及 OverlayFS 类的 Union FS 等技术,
          对进程进行封装隔离,属于 操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。
      
  • Docker和传统虚拟化方式(虚拟机技术)的不同
  •       传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;
          容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。
      
    作为一种新兴的虚拟化方式,Docker 跟传统的虚拟化方式相比具有众多的优势。
  • 为什么要用Docker
  • ・更高效的利用系统资源;
           ・更快速的启动时间;
           ・一致的运行环境;
           ・持续交付和部署;
             ---> 开发通过Dockerfile构建镜像,并结合持续集成系统进行集成测试,甚至结合持续部署系统进行自动部署。
           ・更轻松的迁移;
           ・更轻松的维护和扩展.
             ---> Docker使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。
             此外,Docker团队同各个开源项目团队一起维护了一大批高质量的官方镜像,既可以直接在生产环境使用,又可以作为基础进一步定制,大大的降低了应用服务的镜像制作成本。
    
          特性                容器                   虚拟机
          启动                秒级                   分钟级
          硬盘使用             一般为 MB              一般为 GB
          性能                接近原生                弱于
          系统支持量           单机支持上千个容器       一般几十个
      

基本概念

理解了这三个概念,就理解了 Docker 的整个生命周期。
  • 镜像
  • image
  • 容器
  • container
    '镜像与容器'类似'类与实例'的关系。 容器运行时 = 镜像(基础层) + 运行时的读写(容器存储层),但Docker最佳实践的要求是所有文件写入操作,都应该使用数据卷(Volume)或者绑定宿主目录。因为容器和存储层是有生存周期的。
            查看镜像:docker image ls 或 nvidia-docker image ls
            查看容器:docker ps -a
          
  • 仓库
  • repository
    需要一个集中的存储、分发镜像的服务 ———— Docker Registry
                服务
                Registry
                │
                |   仓库
                |—— Repository 1
                |—— Repository 2
                |   . . .
                └───Repository n
                    |
                    │   标签
                    │—— Tag 1
                    |—— Tag 2
                    |   . . .
                    └—— Tag n ———— Each tag corresponds to a image.
    
                比如一个仓库包含同一个软件不同版本的镜像。
                <仓库名>:<标签> 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。
                例:
                ubuntu:16.04, ubuntu:18.04, ubuntu:latest
                仓库名经常以 两段式路径 形式出现,如 devonnhou/pytorch1.5
    
                最常使用的 Registry 公开服务是官方的 Docker Hub,这也是默认的 Registry,并拥有大量的高质量的官方镜像。
                 Docker Hub 
                国内加速器下载Docker Hub的镜像更快,如阿里云加速器
          

实战篇

https://zhuanlan.zhihu.com/p/23599229

安装和卸载

Docker可以安装在Windows、Linux、Mac等各个平台上。具体可以查看文档Install Docker
安装完成之后,可以查看Docker的版本信息:# docker version
查看Docker的帮助信息:# docker --help。各种命令的用法也不再赘述,后边用到哪些命令时会作出一定的解释。

关于镜像的基本操作

    在dockerhub选择镜像时,比如为pytorch构建的不同tag的cuda镜像区别:
    base:基于cuda,包含最精简的依赖,用于部署预编译的cuda应用,需要手动安装所需的其他依赖
    runtime:基于base,添加了cuda toolkit共享的库,涵盖了运行环境的最小集合如动态库等,但没有cuda的编译工具nvcc
    devel:基于runtime,添加了编译工具链、调试工具、头文件、静态库,用于从源码编译cuda应用,是有nvcc的
  

关于容器的基本操作

      docker run -it --runtime=nvidia -p 9999:22 --restart=always --ipc=host -v /data/houdewang/containers-shared:/containers-shared --name=super-resolution hdw-pytorch:1.7.0 bash
      通过宿主机的9999端口可以ssh登录到容器中 (和vscode的remote-SSH功能搭配使用,很方便)
      -v 把宿主机上的目录映射到容器内
      这样进去的用户名默认都是root,如果为了方便主环境管理员,可以把起容器的命令改进,加一组参数-v /etc/passwd:/etc/passwd -u $(id -u):$(id -g),使得容器内外的用户名一致。
  
      退出容器并结束容器运行 exit
      返回容器 先start,再attach
      退出容器但容器仍运行 Ctrl+P+Q
      返回容器 docker attach container_id (或 docker exec -it container_id bashShell)
      进不去使用 docker exec -it containerID /bin/bash
  
    docker存储的维护:
    docker system df 查看存储占用情况,
    docker rmi IMAGE 指定删除镜像
    docker rm  CONTAINER 指定删除容器
    docker system prune = docker image prune + docker container prune + docker network prune 自动清理未使用的镜像/容器/网络等。
  
      本地浏览器连容器内的tensorboard:
      1.创建容器时 -p 6166:6166/tcp -p 6167:6167/tcp 多映射几对端口。(平时9999:22,是因为ssh一般默认22端口)
      2.tensorboard --logdir dirname --bind_all --port 6167,可以tensorboard -h查看--bind_all的作用,可以知道它能将TensorBoard实例暴露给公网。
      3.本地浏览器输入 http://服务器IP:6166
  
    修改虚拟网卡ip: 配置文件添加 --bip,设置合适的地址。不然会报错。
    step1: 删除原有配置
    https://www.jianshu.com/p/c06d532ec8f8
    step2: 修改docker配置
    https://stackoverflow.com/questions/52225493/change-default-docker0-bridge-ip-address

  
      注意:在docker内查看nvidia-smi是不显示进程号(pid), 回到服务器账户看就好啦
  

关于在远程容器中使用jupyter

    对于一些教程类型的code,我们经常需要用到jupyter。那么如何在本地利用远程服务器的算力和docker容器启动jupyter呢?
    step1: 首先创建容器时,就记得提前多留出一个特定端口
    docker run -it --runtime=nvidia -p 9999:22 10000:8888 ...

    step2: 安装jupyter并进行配置
    安装:pip install notebook
    生成配置文件:jupyter notebook --generate-config
    修改配置:vim ~/.jupyter/jupyter_notebook_config.py
    c.NotebookApp.ip='*'    #表示同一网络的主机都可访问
    c.NotebookApp.password = u'sha1密文'
    c.NotebookApp.open_browser = False
    c.NotebookApp.port =8888    #随便指定一个

    第二行的密码是可选的,为了安全起见,我们可以生成一个。
    In [1]: from notebook.auth import passwd
    In [2]: passwd()
    Enter password:
    Verify password:
    # 粘贴到c.NotebookApp.password = u'sha1密文'这儿
    Out[2]:'sha1:xxxxxxxxx:xxxxxxxxxxxxxxxxxxxxx'

    step3: 启动jupyter并在本地浏览器打开:
    启动:jupyter notebook
    url:服务器IP:10000 (对应创建容器时的-p设置)

    step4: 输入密码

  

关于vscode进入远程容器

    设置管理员密码
    passwd root

    enable管理员远程登录
    sudo service ssh start
    sudo vim /etc/ssh/sshd_config
    PermitRootLogin 改为yes

    容器内open-ssh开机自动启动
    sudo service ssh restart
    sudo apt update && apt install systemd && systemd enable ssh

    Open SSH Configuration File... HostName和宿主机一致,端口用9999
    Connect to Host...
  

Kubernetes(常简称为K8s)旨在提供跨主机集群的平台。它支持一系列容器工具,包括Docker等。

Kubectl基本命令

     假设资源组namespace叫video
    ・ cd k8s folder
    ・ Run: sudo kubectl apply -f 
    ・ Check status: sudo kubectl get pod -n video
    ・ Check real-time logs: sudo kubectl logs  -n video
    ・ Stop: sudo kubectl delete –f 

    (Pod: Kubernetes的基本调度单元称为“pod”。)
  
    同时可以在~/.bashrc定义快捷方式

    # shortcut for kubectl commands
    alias kapply="sudo kubectl apply -f"
    alias kstat="sudo kubectl get pod -n video"
    alias kdel="sudo kubectl delete -f"
    alias klog="sudo kubectl logs -n video -f"

    最后一个添加了-f,会stream log (持续更新log),不加-f是一次性显示那一时刻的log。
  
    最后,最自由的使用方式是进入容器内。
    sudo kubectl exec -it  -n video -- /bin/bash 进 exit 出

    也可以定义快捷方式
    alias kexec='AttachDocker(){ sudo kubectl exec -it $1 -n video -- /bin/bash;};AttachDocker'
    进容器后做什么都可以,但不要kill这个pod任务里的进程,不然当前pod会报Error,然后K8s又自动重新帮你开一个新的pod。
    可以在启动脚本里加一句 date;sleep 2m;date 搭配使用,在训练开始的2分钟前,kexec进去安装或修改一些必要的库。
  
      查看节点gpu数量:
      kubectl get nodes "-o=custom-columns=NAME:.metadata.name,GPU:.status.allocatable.nvidia\.com/gpu"
      查看现有任务所用的节点:
      sudo kubectl get pod -n kube-system -o wide -n video
  

搭配NFS存储服务器

    现在比较流行的是挂载NFS网络盘,适合深度学习批量传输小文件的需求。
    sudo mount -t nfs -o rw xx.xx.xx.xx(IP):/export/xx(remote directory) /home/xx(local directory)

    mount [-t vfstype] [-o  options] device dir
    其他参数见 https://www.cnblogs.com/linuxprobe/p/5473645.html
  

docker存储清理

    docker system prune -a && docker volume prune
    前一个命令安全,后一个不到不得已慎用。
    BE CAREFUL docker volume prunewill remove all your data that was persisted from docker to host disk........ system prune is safe though
  

docker存储迁移

docker安装时,存储位置默认在/var/lib/docker。一般这个是挂载在系统盘。而系统盘一般空间较小,无法满足docker用户增长的需求。并且出问题会带来一些风险。放数据盘更安全。
    1. sudo systemctl stop docker 且 systemctl stop docker.socket
    2. sudo cp -rp /var/lib/docker/* /新的存储位置/ 或 sudo rsync -aqxP /var/lib/docker/ /新的存储位置/
       3. sudo rm -rf /var/lib/docker
       4. sudo ln -s /新的存储位置/ /var/lib/docker
       或
       3. 更新docker配置文件 /etc/docker/daemon.json
       {
        "data-root": "/新的存储位置/"
       }
       4. 保存和关闭配置文件
    5. sudo systemctl start docker
  

制作docker镜像

How to write dockerfile
    除了使用docker镜像,很多时候我们需要制作镜像,来完成服务器部署。
    常见流程:编写dockerfile→jenkins编译→发布到jfrog
  

由容器保存镜像

通过容器提交镜像(docker commit)以及推送镜像(docker push)笔记