Docker

Docker

Posted by nomadli on February 20, 2017

原理

  1. namespace 六种隔离 #include clone(int(*func)(void*), 线程函数 void *stackbuf, 线程函数的栈buf int flags, 下面的6种隔离的位或 void *arg, ...) 函数参数 PID CLONE_NEW_PID 隔离进程编号 UTS CLONE_NEWUTS 运行时系统信息 IPC CLONE_NEWIPC 进程通讯相关的,信号量、消息队列、内存映射 Network CLONE_NEWNET 网络协议栈、ARP表、路由表、NAT表 Mount CLONE_NEWNS 文件系统挂载点 User CLONE_NEWUSER 用户、组名

  2. CGroup 资源隔离 创建或下载镜像文件系统github.com/CentOS/sig-cloud-instance-images 在clone的线程函数中执行 在解压目录(root_path)创建proc目录修改权限为0x755 mount(“proc”, proc_path, “proc”) mount(“root”, root_path, “”, MS_BIND|MS_REC) 在root_path创建.pivot_root目录权限0x700 pivot_root(root_path, pivot_root_path) umoount(“/.pivot_root”, MNT_DETACH)

基本命令

  • docker –version
  • docker-compose –version
  • docker-machine –version
  • docker pull xxx 下载image xxx
  • docker build –no-cache -t xxx:xxx . 创建image
  • docker images [dangling=true] 显示已有[未打标签]的image
  • docker rmi xxx 删除image xxx
  • docker run -itd -p host80:80 -h host -v host/path:path –entrypoint xx/xx.sh –name xx xx:xx
  • docker run –rm 退出时删除容器磁盘 等价于退出后执行 docker rm -v
  • docker run –link name:xx 在新的容器中使用xx链接另一个容器name, 如mysql -h xx -uadmin -p1234
  • docker run –net 指定网络模式
    • docker network create -d bridge name 创建一个新的网络namespace
    • –net=host 使用的网络实际上和宿主机一样
    • –net=container:container_id/container_name多个容器使用共同的网络,看到的ip是一样的
    • –net=none 不分配置任何网络。
    • –net=bridge 默认模式每个容器分配一个独立的network namespace
    • –net= network overlay
  • docker run -e xxx=xx 设置环境变量
  • docker run –restart=

      docker run --restart=no             默认策略,在容器退出时不重启容器
      docker run --restart=on-failure     在容器非正常退出时(退出状态非0),才会重启容器
      docker run --restart=on-failure:3   在容器非正常退出时重启容器,最多重启3次
      docker run --restart=always         在容器退出时总是重启容器
      docker run --restart=unless-stopped 总是重启容器,但不启动Docker守护进程启动时就已经停止的
      docker inspect -f "" name 查看重启次数
      docker inspect -f "" name 查看最后一次启动时间
      状态码
      125     Docker守护进程本身的错误
      126     容器启动后,要执行的默认命令无法调用
      127     容器启动后,要执行的默认命令不存在
      其它     容器中异常程序返回的错误码
    
  • docker info 容器信息
  • docker ps 展示目前启动的容器
  • docker ps -a 展示所有容器
  • docker inspect xx 显示当前容器详细配置

      docker inspect -f  当前容器卷
    
  • docker start 启动容器
  • docker stop 停止容器
  • docker kill –signal=”SIGUSR1”
  • docker rm -f webservr 停止并删除容器,但不会删除镜像
  • docker exec -it xxx /bin/bash 运行容器并进入shell -u root 以root用户
  • docker attach xxx 进入运行中的容器
  • docker commit xxx xx:xx 将content打包为镜像带layer
  • docker export xxx -o xxx.tar 将content转为xxx.tar不带layer
  • docker save -o xxx.tar xxx:xx 将容器镜像打包
  • docker load -i xxx.tar 加载容器镜像
  • cat xxx.tar docker import - xxx:xx
  • /etc/docker/daemon.json “registry-mirrors”:[“http://hub-mirror.c.163.com”],
  • docker network ls / docker network create foo
  • docker run -d –privileged=true xxx /usr/sbin/init 启动服务并允许容器使用systemctrl 启动服务
  • docker system prune 清理资源

Dockerfile

  1. FROM 基础镜像
  2. MAINTAINER 作者信息 邮箱要用””
  3. USER 指定运行时用户
  4. WORKDIR 指定Dockerfile命令运行目录
  5. ADD 将host文件拷贝到容器内
  6. RUN 执行命令
  7. ENV 设置环境变量,ssh登陆后不起作用,修改/etc/profile添加环境
  8. EXPOSE 导出端口
  9. CMD ENTRYPOINT的参数
  10. COPY
  11. LABEL
  12. STOPSIGNAL
  13. VOLUME
  14. ENTRYPOINT 容器启动时执行的命令
  15. ARG
  16. HEALTHCHECK
  17. SHELL

docker-compose

  1. pip install docker-compose
  2. docker-compose -f x.yml -p 项目名 –x-networking docker网络模型
  3. docker-compose build –force-rm 删除临时 –no-cache 不用已有layer –pull 构建容器镜像
  4. docker-compose config 验证 yml文件
  5. docker-compose down 停止容器并删除网络
  6. docker-compose exec name 进入指定容器
  7. docker-compose images 显示yml中的镜像
  8. docker-compose kill -s SIGINT 发送信号 默认SIGTERM
  9. docker-compose logs –no-color 因为不同容器不同颜色
  10. docker-compose pause 暂停容器
  11. docker-compose port –protocol=tcp/udp –index=(同一image多个容器1,2) 显示映射端口
  12. docker-compose ps -q 显示所有容器
  13. docker-compose pull –ignore-pull-failures 忽略错误拉取镜像
  14. docker-compose push
  15. docker-compose restart -t 超时
  16. docker-compose rm -f 强制 -v 删除挂载卷
  17. docker-compose run –no-deps 不启动依赖 -d –name –entrypoint -e 环境变量 -u 用户 –rm 执行后自动删除 -p 端口映射 –service-ports 映射配置中的端口 -T 不启动tty name cmd
  18. docker-compose scale name=num -t 超时 指定服务的容器数量(扩缩容)
  19. docker-compose start
  20. docker-compose stop -t
  21. docker-compose top 查看容器内进程
  22. docker-compose unpause 恢复
  23. docker-compose up -d –no-recreate –force-recreate –no-color –no-deps –no-build -t 构建并启动
  24. yml 语法 ${xxx} 可以获取参数
    version:'2'                 yml版本
    cgroup: xxx                 定义一个组通用内存、cpu、磁盘等限制配置
    services:                   定义容器运行参数
    xxx:                    服务名称
        image: xxx:xx       此容器依赖的image名称或id
        build:              指定通过某目录的Dockerfile来生成image并命名为image指定名
            context: xx/    指定文件目录
            dockerfile: x/x 指定Dockerfile
            args:           指定构建image时的环境变量,不进入容器
                - xx=x      值可以为空,在构建image过程中赋值
            cache_from:
                - mogo:3    指定构建缓存
        command: xxx        覆盖CMD 中的值
        container_name: xx  容器名称,否则为 项目名服务名序号
        depends_on:         依赖的服务
            - xx
        dns:
            - x.x.x.x
        dns_search:
            - xx.xx.com
        tmpfs:              挂载零时目录
            - /tmp
        entrypoint: xx/xx/x
        env_file:           指定变量文件,这些变量不针对build
            - xx/xx.env
        environment:        指定容器内环境变量,类Dockerfile的ENV
            - xx=xx
        expose:             容器间的导出端口
            - "80"
        ulimits: 
            nproc:          最大进程数
                soft: xxx   警告
                hard: xxx   上限
            memlock:        内存限制
                soft: xxx   警告
                hard: xxx   上限
            nofile: 
                soft: xxx   警告
                hard: xxx   上限
        external_links:     链接非compose启动的容器
            - xxx
        extra_hosts:        给容器添加hosts映射
            - "name:ip"
        labels:             给容器添加标签,docker info时查看
            - "xx=xx"
        links:              链接compose启动的容器
            - xx:yy         yy为xx的别名
        logging:            配置日志服务
            driver:syslog   默认是json-file, compose可以显示json-file和journald其它需要工具查看
            options:
                syslog-address: "tcp://ip"
                max-size: "200k"
                max-file: "10"
        pid: "host"         进程命名空间,host将与宿主机共享
        ports:              端口映射
            - "host:conten"
            - "ip:port:port"
        stop_signal: SIGUSR1默认停止容器信号是SIGTERM
        volumes:            挂载外部文件系统
            - host:cont:ro  只读 rw
            - lvname:path   挂载数据卷
            - driver:driver 挂载磁盘
        volumes_from:       挂载其它容器磁盘
            - name
            - container:name
        cap_add:            添加内核功能
            - "SETPCAP"
            - "SYS_MODULE"
            - "SYS_RAWIO"
            - "SYS_PACCT"
            - "SYS_ADMIN"
            - "SYS_NICE"
            - "SYS_RESOURCE"
            - "SYS_TIME"
            - "SYS_TTY_CONFIG"
            - "MKNOD"
            - "AUDIT_WRITE"
            - "AUDIT_CONTROL"
            - "MAC_OVERRIDE"
            - "MAC_ADMIN"
            - "NET_ADMIN"
            - "SYSLOG"
            - "CHOWN"
            - "NET_RAW"
            - "DAC_OVERRIDE"
            - "FOWNER"
            - "DAC_READ_SEARCH"
            - "FSETID"
            - "KILL"
            - "SETGID"
            - "SETUID"
            - "LINUX_IMMUTABLE"
            - "NET_BIND_SERVICE"
            - "NET_BROADCAST"
            - "IPC_LOCK"
            - "IPC_OWNER"
            - "SYS_CHROOT"
            - "SYS_PTRACE"
            - "SYS_BOOT"
            - "LEASE"
            - "SETFCAP"
            - "WAKE_ALARM"
            - "BLOCK_SUSPEND"
        cap_drop:           删除内核功能
            - SYS_ADMIN
        secrets:            敏感信息
            - dbpassword
        security_opt:       默认标签
            - label: xx:yy
        sysctls:            容器内核参数
            - net.core.somaxconn=1024
        healthcheck: 
            test: ["CMD", "culr", "-f", "http://localhost"]
            interval: 1m30s
            timeout: 10s
            retries: 3
        cgroup_parent: name 指定父级组
        devices:            设备隐射
            - "/dev/ttyUSB0:/dev/ttyUSB0"
        extends:            导入额外的yml文件
            file:xx/xx.yml
            service:xxx     可以只针对某服务
        network_mode: "xx"  网络模式 bridge host none service:name container:name/id
        networks:           加入指定网络
            xxx:            网络名称
                aliases:
                    - 别名1
        cpu_shares: 73
        cpu_quota: 50000
        cpuset: 0,1
        user: postgresql
        working_dir: /code
        domainname: foo.com
        hostname: foo
        ipc: host
        mac_address: 02:42:ac:11:65:43
        mem_limit: 100M
        memswap_limit: 2000000000
        privileged: true
        restart: always
        read_only: true
        shm_size: 64M
        stdin_open: true
        tty: true
    

    生成无历史layer image

  25. 写Dockerfile
  26. docker build –no-cache -t xx:temp .
  27. docker run -itd –name xx xx:temp
  28. docker stop xx
  29. docker export xx -o xx_xx.tar
  30. docker rm -f xx
  31. docker rmi xx:temp
  32. cat xx_xx.tar docker import - xx:xx
  33. rm -rf xx_xx.tar

centos ssh

  • yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-selinux docker-engine-selinux docker-engine
  • curl -o /etc/yum.repos.d/docker-ce.repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
  • yum makecache && yum -y update && yum -y install docker-ce
  • vi /usr/lib/systemd/system/docker.service ExecStart=/usr/bin/dockerd -H fd:// –containerd=/run/containerd/containerd.sock –data-root /vdb/docker
  • systemctl enable docker && systemctl daemon-reload && systemctl restart docker
  • wget http://mirrors.163.com/.help/CentOS7-Base-163.repo
  • DockerFile FROM centos:7 MAINTAINER nomadli “dzym79@qq.com” WORKDIR /root ADD CentOS7-Base-163.repo /etc/yum.repos.d/ RUN mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.back RUN mv /etc/yum.repos.d/CentOS7-Base-163.repo /etc/yum.repos.d/CentOS7-Base.repo RUN yum clean all RUN yum makecache RUN yum -y update RUN yum -y install openssh-server RUN yum clean all RUN echo “root:nomadli” | chpasswd RUN sed -i ‘s/UsePAM yes/UsePAM no/g’ /etc/ssh/sshd_config RUN sed -i ‘s/#PermitRootLogin yes/PermitRootLogin yes/g’ /etc/ssh/sshd_config RUN sed -i ‘s/#UsePrivilegeSeparation sandbox/UsePrivilegeSeparation no/g’ /etc/ssh/sshd_config RUN ssh-keygen -q -t rsa -b 2048 -f /etc/ssh/ssh_host_rsa_key -N ‘’ RUN ssh-keygen -q -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key -N ‘’ RUN ssh-keygen -t dsa -f /etc/ssh/ssh_host_ed25519_key -N ‘’ RUN echo “#!/bin/bash” > /root/init.sh RUN echo “/usr/sbin/sshd -f /etc/ssh/sshd_config” » /root/init.sh RUN echo “/bin/bash” » /root/init.sh RUN chmod +x /root/init.sh ENTRYPOINT [“/bin/bash”, “-c”] CMD [“/root/init.sh”]

windows

  • widow编译工具容器教程
  • 启用 Hyper-V

      PowerShell Systeminfo.exe 查看系统信息
        
      Windows 10 企业版、专业版或教育版
      具有二级地址转换(SLAT)的64位处理器
      CPU支持VM监视器模式扩展(Intel CPU 上的 VT-c)。
      >=4GB内存
      1. 管理员 PowerShell 运行 Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All 重启 搞定
      2. DISM /Online /Enable-Feature /All /FeatureName:Microsoft-Hyper-V 重启 搞定
      3. 控制面板 应用和功能 打开或关闭Windows功能 选Hyper-V 重启 搞定
    
      Windows Server 2016 2008
      具有二级地址转换(SLAT)的64位处理器
      CPU支持VM监视器模式扩展(Intel CPU 上的 VT-c)。
      >=4GB内存
      最多虚拟50台
      服务器管理器 添加角色和功能 基于角色 或 基于功能的安装 Hyper-V 
      或 管理员 PowerShell 运行  Install-WindowsFeature -Name Hyper-V -ComputerName <computer_name> -IncludeManagementTools -Restart
    
  • 安装 Docker for Windows

      Windows 10
      进高级 配置
      {
          ...
          "storage-opts": [
              "size=120GB"
          ]
          ...
      }
        
      Windows Server 2016
      sc.exe stop docker
      %ProgramData%\Docker\config\daemon.json 添加上面的json
      sc.exe start docker
    
  • Dockerfile

      FROM microsoft/windowsservercore
        
      # wget https://dist.nuget.org/win-x86-commandline/v4.1.0/nuget.exe
      ADD nuget.exe C:\\Bin\\nuget.exe
    
      # wget https://aka.ms/vs/15/release/vs_buildtools.exe
      ADD vs_buildtools.exe C:\\TEMP\\vs_buildtools.exe
    
      RUN setx /m PATH "%PATH%;C:\Bin" \
      && C:\TEMP\vs_buildtools.exe --quiet --wait --norestart --nocache --installPath C:\BuildTools --all \
      --remove Microsoft.VisualStudio.Component.Windows10SDK.10240 \
      --remove Microsoft.VisualStudio.Component.Windows10SDK.10586 \
      --remove Microsoft.VisualStudio.Component.Windows10SDK.14393 \
      --remove Microsoft.VisualStudio.Component.Windows81SDK \
      || IF "%ERRORLEVEL%"=="3010" EXIT 0
    
      ENTRYPOINT C:\BuildTools\Common7\Tools\VsDevCmd.bat &&
    
      CMD ["powershell.exe", "-NoLogo", "-ExecutionPolicy", "Bypass"]
    
  • docker build -t buildtools2017:latest -m 2GB //内存上限 默认 1G
  • Windows容器不支持交互式用户界面
  • FROM microsoft/aspnet .Net
  • vs命令行构建工具ID

log

  • –log-driver= none json-file syslog gelf fluentd awslogs splunk etwlogs gcplogs nats
  • github.com/AliyunContainerService/log-pilot
  • github.com/Graylog2/graylog2-server

centos images

#!/usr/bin/env bash

yum install -y supermin5

supermin5 --prepare bash coreutils find more -o supermin.d  
supermin5 --build --format chroot supermin.d -o appliance.d  
rm -rf   supermin.d

pushd appliance.d  
    mv usr/share/locale/en usr/share/locale/en_US tmp  
    rm -rf usr/share/locale/*  
    mv tmp/en tmp/en_US usr/share/locale/  
    mv usr/share/i18n/locales/en_US tmp  
    rm -rf usr/share/i18n/locales/*  
    mv tmp/en_US usr/share/i18n/locales/  
    echo 7.5 > etc/yum/vars/releasever
    rm -rf /var/cache/* /tmp/* /var/log/*
    find . -name "*.log" -exec rm -rf {} \;

    pushd lib64
    rm -rf $(ls -d */ | grep -v lib64)
    popd
    rm -rf usr/share/*
    rm -rf usr/bin/bashbug usr/bin/bashbug-64 usr/bin/dir
    rm -rf etc/services
    cp /usr/bin/find usr/bin/
    cp /usr/bin/more usr/bin/

    cp /root/.bash* /root/.cshrc /root/.tcshrc root
popd  
  
tar --numeric-owner -cpf centos7-zero.tar -C appliance.d .  
cat centos7-zero.tar | docker import - centos7:base
rm -rf centos7-zero.tar appliance.d
docker rmi $(docker images -q -f dangling=true) || echo "no old image"

developer

  • 名字空间
    • Cgroup CLONE_NEWCGROUP root directory,对一组进程进行统一资源监控和限制
      • task: 当前组里的一个进程
      • subsystem 一个关联到一个cgroup的内核模块,做具体的监控和操作,Linux支持13种subsystem
      • cgroup 某种资源控制标准划分而成的任务组,包含一个或多个subsystem
      • hierarchy 一棵cgroup树,包含系统中的所有进程.可以有很多hierarchy与不同的subsystem关联.Linux最多13个hierarchy,也可以只建一个hierarchy关联所有的subsystem.当一个hierarchy不与任何subsystem关联只是将进程进行分组,程序可以自己创建hierarchy用来分组自己的子进程.
    • IPC CLONE_NEWIPC System V IPC, POSIX message queues
    • Network CLONE_NEWNET Network devices, stacks, ports, etc.
    • Mount CLONE_NEWNS Mount points
    • PID CLONE_NEWPID Process IDs
    • Time CLONE_NEWTIME Boot and monotonic clocks
    • User CLONE_NEWUSER User and group IDs
    • UTS CLONE_NEWUTS Hostname and NIS domain name
  • 系统调用
    • clone CLONE_NEW* 创建新进程,通过flag指定新建名字空间
    • setns /proc/[pid]/ns 加入指定的名字空间
    • unshare CLONE_* 进程中调用,创建新命名空间 CLONE_FILES: 不想共享父进程的文件描述符了 CLONE_FS: 文件系统信息; CLONE_SYSVSEM: SYS V信号量; CLONE_NEWIPC: IPC Namespace
    • ioctl 间接调用ioctl_ns 查询名字空间信息
  • /proc/sys/user 名字空间中的每用户限制
  • /proc/[pid]/ns 进程使用的名字空间

shell

#nsenter    加入指定进程的指定IPC namespace中
#unshare    离开当前IPC namespace,创建并加入新的IPC namespace
#ipcmk      创建IPC:shared memory segments、message queues、semaphore arrays
#ipcs       查看IPC:shared memory segments、message queues、semaphore arrays

echo $$                                     #当前PID
readlink /proc/$$/ns/uts /proc/$$/ns/ipc    #查看当前控制台的uts和ipc
ipcmk -Q                                    #创建一个message queues
ipcs -q                                     #查看当前的message queues列表
unshare -iu /bin/bash                       #退出当前namespace并进入新建namespace
nsenter -t PID -u -i /bin/bash              #进入另一个shell的namespace

#ethtool -k     查看netns-local属性
#ip             网络管理 ip link 网络设备管理
#ip netns       管理network namespace
#ip netns list
#ip netns add name
#ip netns set name NETNSID
#ip [-all] netns delete name

modprobe veth                                       #加载虚拟网桥内核模块
ip link add vir_eth11 type veth peer name vir_eth12 #创建一个虚拟网桥一端叫vir_eth11另一端vir_eth12
ip a                                                #查看当前全部网卡设备
ip netns add net_name01                             #添加一个网络命名空间
ip netns add net_name02                             #添加一个网络命名空间
ip netns list                                       #查看当前网络命名空间
ip link set vir_eth11 netns net_name01              #设置网桥vir_eth11端的网络空间为net_name01
ip link set vir_eth12 netns net_name02              #设置网桥vir_eth12端的网络空间为net_name02
ip a                                                #查看当前全部网卡设备
nsenter --net=/var/run/netns/net_name01 /bin/bash   #将bash的网络空间改为net_name01
ip a
ip addr add 192.168.0.1/24 dev vir_eth11            #设置IP
ip link set dev vir_eth11 up                        #启动网桥
ip a
#另一个shell加入net_name02同样操作, ping 192.168.0.1

#Cgroup
cat /proc/cgroups                   #查看目前Cgroup
cat /proc/$$/cgroup                 #查看自己的cgroup hierachy:subsystem,subsystem:挂载点路径
mount |grep cgroup                  #查看当前系统cgroup挂载情况
#如果没挂载,可以手动挂载
mkdir /sys/fs/cgroup
mount -t tmpfs cgroup_root /sys/fs/cgroup
mkdir /sys/fs/cgroup/cpuset
mount -t cgroup cpuset -o cgroup /sys/fs/cgroup/cpuset/
mkdir /sys/fs/cgroup/cpu_memory
mount -n -t cgroup -o cpu,memory cgroup /sys/fs/cgroup/cpu_memory

yum install cgroup-tools
cgcreate                            #创建Cgroup
cgset                               #设置参数
cgexec                              #在指定的cgroup运行命令

#rootfs 操作系统所在的文件系统,系统启动时只读,启动完成后改为读写
#union mount 在已挂载的文件系统上再挂载一个文件系统级联方式 overlay2
#overlay2 分三个目录 lower只读目录, 可以有很多个;upper只有一个读写层;work工作目录对用户不可见
#overlay2 有一个视图merged, 合并lower和upper用于展示
mkdir upper lower merged work
echo "lower" > lower/a.txt
echo "upper" > upper/b.txt
echo "lower" > lower/c.txt
echo "upper" > upper/c.txt
tree .
mount -t overlay overlay -o lowerdir=lower,upperdir=upper,workdir=work merged
mount |grep overlay
tree .
cat merged/c.txt
touch merged/d.txt
tree .
rm -f merged/c.txt
tree .
ll upper/c.txt


#脚本实现一个容器
#!/usr/bin/env bash
set -o errexit  #错误立即退出 set -e
set -o nounset  #使用未初始化参数立刻退出 set -u
set -o pipefail # xx|xx|xx 任意一个命令失败都立刻退出
set -o echo-before-execute #执行前显示将执行的命令,有时命令错误会提示正确写法; set -x
#shopt -p 显示当前所有设置值 shopt -u xxx 取消设置
shopt -s nullglob #没有匹配任何文件的文件名转为空白
overlay_path='/data/vir/overlay'
container_path='/data/vir/containers'
cgroups='cpu,cpuacct,memory'

if [[ $# -gt 0 ]]; then
    while [ "${1:0:2}" == '--' ]; do
        OPTION=${1:2}
        if [[ $OPTION =~ = ]]; then
            declare "BOCKER_${OPTION/=*/}=${OPTION/*=/}" || declare "BOCKER_${OPTION}=x"
            shift
        fi
    done
fi
 
function bocker_check() {
    case ${1:0:3} in
        img) ls "$overlay_path" | grep -qw "$1" && echo 0 || echo 1;;
        ps_) ls "$container_path" | grep -qw "$1" && echo 2 || echo 3;;
    esac
}

#shuf对输入随机选择并输出指定个输出
function bocker_init() { #HELP Create an image from a directory:\nBOCKER init <directory>
    uuid="img_$(shuf -i 42002-42254 -n 1)"
    if [[ -d "$1" ]]; then
        [[ "$(bocker_check "$uuid")" == 0 ]] && bocker_run "$@"
        mkdir "$overlay_path/$uuid" > /dev/null
        cp -rf --reflink=auto "$1"/* "$overlay_path/$uuid" > /dev/null
        [[ ! -f "$overlay_path/$uuid"/img.source ]] && echo "$1" > "$overlay_path/$uuid"/img.source
        [[ ! -d "$overlay_path/$uuid"/proc ]] && mkdir "$overlay_path/$uuid"/proc
        echo "Created: $uuid"
    else
        echo "No directory named '$1' exists"
    fi
}

#uuidgen 生成随机UUID
#https://github.com/moby/moby download-frozen-image-v2.sh 从hub.docker.com 拉取镜像
#yum -y install jq  json数据分析
function bocker_pull() { #HELP Pull an image from Docker Hub:\nBOCKER pull <name> <tag>
    tmp_uuid="$(uuidgen)" && mkdir /tmp/"$tmp_uuid"
    download-frozen-image-v2 /tmp/"$tmp_uuid" "$1:$2" > /dev/null
    rm -rf /tmp/"$tmp_uuid"/repositories
    for tar in $(jq '.[].Layers[]' --raw-output < /tmp/$tmp_uuid/manifest.json); do
        tar xf /tmp/$tmp_uuid/$tar -C /tmp/$tmp_uuid && rm -rf /tmp/$tmp_uuid/$tar
    done
    for config in $(jq '.[].Config' --raw-output < /tmp/$tmp_uuid/manifest.json); do
        rm -f /tmp/$tmp_uuid/$config
    done
    echo "$1:$2" > /tmp/$tmp_uuid/img.source
    bocker_init /tmp/$tmp_uuid && rm -rf /tmp/$tmp_uuid
}
 
function bocker_rm() { #HELP Delete an image or container:\nBOCKER rm <image_id or container_id>
    [[ "$(bocker_check "$1")" == 3 ]] && echo "No container named '$1' exists" && exit 1
    [[ "$(bocker_check "$1")" == 1 ]] && echo "No image named '$1' exists" && exit 1
    if [[ -d "$overlay_path/$1" ]];then
        rm -rf "$overlay_path/$1" && echo "Removed: $1"
    else
        umount "$container_path/$1"/merged && rm -rf "$container_path/$1" && ip netns del netns_"$1" && ip link del dev veth0_"$1" && echo "Removed: $1"
        cgdelete -g "$cgroups:/$1" &> /dev/null
    fi
     
}
 
function bocker_images() { #HELP List images:\nBOCKER images
    echo -e "IMAGE_ID\t\tSOURCE"
    for img in "$overlay_path"/img_*; do
        img=$(basename "$img")
        echo -e "$img\t\t$(cat "$overlay_path/$img/img.source")"
    done
}
 
function bocker_ps() { #HELP List containers:\nBOCKER ps
    echo -e "CONTAINER_ID\t\tCOMMAND"
    for ps in "$container_path"/ps_*; do
        ps=$(basename "$ps")
        echo -e "$ps\t\t$(cat "$container_path/$ps/$ps.cmd")"
    done
}
 
function bocker_run() { #HELP Create a container:\nBOCKER run <image_id> <command>
    uuid="ps_$(shuf -i 42002-42254 -n 1)"
    [[ "$(bocker_check "$1")" == 1 ]] && echo "No image named '$1' exists" && exit 1
    [[ "$(bocker_check "$uuid")" == 2 ]] && echo "UUID conflict, retrying..." && bocker_run "$@" && return
    cmd="${@:2}" && ip="$(echo "${uuid: -3}" | sed 's/0//g')" && mac="${uuid: -3:1}:${uuid: -2}"
    ip link add dev veth0_"$uuid" type veth peer name veth1_"$uuid"
    ip link set dev veth0_"$uuid" up
    ip link set veth0_"$uuid" master br1
    ip netns add netns_"$uuid"
    ip link set veth1_"$uuid" netns netns_"$uuid"
    ip netns exec netns_"$uuid" ip link set dev lo up
    ip netns exec netns_"$uuid" ip link set veth1_"$uuid" address 02:42:ac:11:00"$mac"
    ip netns exec netns_"$uuid" ip addr add 172.18.0."$ip"/24 dev veth1_"$uuid"
    ip netns exec netns_"$uuid" ip link set dev veth1_"$uuid" up
    ip netns exec netns_"$uuid" ip route add default via 172.18.0.1
    mkdir -p "$container_path/$uuid"/{lower,upper,work,merged} && cp -rf --reflink=auto "$overlay_path/$1"/* "$container_path/$uuid"/lower > /dev/null && \
    mount -t overlay overlay \
        -o lowerdir="$container_path/$uuid"/lower,upperdir="$container_path/$uuid"/upper,workdir="$container_path/$uuid"/work \
        "$container_path/$uuid"/merged
    echo 'nameserver 114.114.114.114' > "$container_path/$uuid"/merged/etc/resolv.conf
    echo "$cmd" > "$container_path/$uuid/$uuid.cmd"
    cgcreate -g "$cgroups:/$uuid"
    : "${BOCKER_CPU_SHARE:=512}" && cgset -r cpu.shares="$BOCKER_CPU_SHARE" "$uuid"
    : "${BOCKER_MEM_LIMIT:=512}" && cgset -r memory.limit_in_bytes="$((BOCKER_MEM_LIMIT * 1000000))" "$uuid"
    cgexec -g "$cgroups:$uuid" \
        ip netns exec netns_"$uuid" \
        unshare -fmuip --mount-proc \
        chroot "$container_path/$uuid"/merged \
        /bin/sh -c "/bin/mount -t proc proc /proc && $cmd" \
        2>&1 | tee "$container_path/$uuid/$uuid.log" || true
    ip link del dev veth0_"$uuid"
    ip netns del netns_"$uuid"
}
 
function bocker_exec() { #HELP Execute a command in a running container:\nBOCKER exec <container_id> <command>
    [[ "$(bocker_check "$1")" == 3 ]] && echo "No container named '$1' exists" && exit 1
    cid="$(ps o ppid,pid | grep "^$(ps o pid,cmd | grep -E "^\ *[0-9]+ unshare.*$1" | awk '{print $1}')" | awk '{print $2}')"
    [[ ! "$cid" =~ ^\ *[0-9]+$ ]] && echo "Container '$1' exists but is not running" && exit 1
    nsenter -t "$cid" -m -u -i -n -p chroot "$container_path/$1"/merged "${@:2}"
}
 
function bocker_logs() { #HELP View logs from a container:\nBOCKER logs <container_id>
    [[ "$(bocker_check "$1")" == 3 ]] && echo "No container named '$1' exists" && exit 1
    cat "$container_path/$1/$1.log"
}
 
function bocker_commit() { #HELP Commit a container to an image:\nBOCKER commit <container_id> <image_id>
    [[ "$(bocker_check "$1")" == 3 ]] && echo "No container named '$1' exists" && exit 1
    [[ "$(bocker_check "$2")" == 0 ]] && echo "Image named '$2' exists" && exit 1
    mkdir "$overlay_path/$2" && cp -rf --reflink=auto "$container_path/$1"/merged/* "$overlay_path/$2" && sed -i "s/:.*$/:$(date +%Y%m%d-%H%M%S)/g" "$overlay_path/$2"/img.source
    echo "Created: $2"
}
 
function bocker_help() { #HELP Display this message:\nBOCKER help
    sed -n "s/^.*#HELP\\s//p;" < "$1" | sed "s/\\\\n/\n\t/g;s/$/\n/;s!BOCKER!${1/!/\\!}!g"
}
 
if [[ -z "${1-}" ]]; then
    bocker_help "$0"
    exit 1
fi

case $1 in
    pull|init|rm|images|ps|run|exec|logs|commit) bocker_"$1" "${@:2}" ;;
    *) bocker_help "$0" ;;
esac