一、虚拟化简介 1、虚拟化概述 虚拟化是一种资源管理技术,能够把物理资源转变为逻辑上可以管理的资源,如服务器、网络、内存及存储资源,虚拟化可以打破物理结构内的壁垒,计算元件运行在虚拟的基础上而非真实的基础上,可以扩大硬件的容量,简化软件的重新配置过程,从而让用户可以用比原本的组太更好的方式来应用和管理这些资源。 虚拟化技术允许一个平台同时运行多个操作系统,并且应用程序可以在相互独立的空间内运行而互不影响,从而显著提高计算机的工作效率,它是一个为了简化管理、优化资源的解决方案。 目前企业主流的虚拟化技术包括:KVM、Xen、VMware Esxi、VirtualBox、Docker,虚拟化技术越来越广泛的应用到互联网企业。 2、虚拟化原理 虚拟化解决方案的底部是要进行虚拟化的物理机器,该物理机器可以直接支持虚拟化,或间接支持虚拟化,那么就需要虚拟机管理程序的支持。虚拟机管理程序(virtual machine monitor,VMM),可以看作是平台硬件和操作系统之间的抽象化或者中间件。 VMM为每个虚拟机分配一套数据结构来管理它们的状态,包括虚拟处理器的全套寄存器、物理内存的使用情况、网络设备状态、虚拟设备状态等。 VMM可以对底层(HostOS)硬件资源(物理CPU、内存、磁盘、网卡、显卡等)进行封装、隔离,抽象为另一种形式的逻辑资源,再提供给上层(GuestOS)虚拟机使用,通过虚拟化技术实现的虚拟机被称为GuestOS,而作为GuestOS载体的物理主机称为HostOS(宿主)。 3、虚拟化技术实现的区别 • 完全虚拟化技术,实际上是通过软件实现对操作系统的资源再分配,比较成熟,如KVM、VirtualBox等; • 半虚拟化技术,则是通过代码修改已有的系统,形成一种新的可虚拟化的系统,调用硬件资源去安装多个系统,整体速度上相对高一点。代表产品有Xen; • 轻量级虚拟化,介于完全虚拟化和半虚拟化之间,典型代表有Docker。 二、Docker 虚拟化简介 1、概述 Docker 是一个开源的应用窗口引擎,开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux 机器上,进而实现虚拟化。容器是完全使用沙箱机制的,而且相互之间不会有任何接口,几乎没有性能开销,可以很容易地在机器和数据中心中运行,最重要的是,它们不依赖于任何语言、框架或包括系统。 Docker 项目的目标是实现轻量级的操作系统虚拟化解决方案,Docker 的基础是Linux 容器(Linux container,LXC)等技术,Docker 在LXC技术的基础上进行了进一步的封装,让用户不需要去关心容器的管理,使得操作更为简便,用户操作Dockeer 的容器就像操作一个快速轻量级的虚拟机一样简单。 Docker 虚拟化和传统虚拟化(KVM、Xen等)方式的不同之处在于Docker 虚拟化可以在操作系统层面上直接实现App或者应用虚拟化,即直接在HostOS 上基于VMM启动各种App。直接复用本地主机的操作系统,而传统方式则在硬件的基础上,虚拟GuestOS操作系统,然后在GuestOS 操作系统,然后在GuestOS 操作系统上部署相关的App应用。 ## Docker 虚拟化实施有以下三个概念: • Docker 镜像,Docker 镜像是一个静态模板,与常见的ISO镜像类似,是一个样板,不能直接修改,可以通过封装生成; • Docker 容器,基于Docker 镜像运行启动的应用或系统,称之为一个Docker 容器或者Docker 虚拟机; • Docker 仓库,Docker 仓库是存放Docker 镜像的地方,常见分为公开仓库(public)和私有仓库(private)两种形式。 2、Docker LXC 之 Cgroup 资源控制 cgroups(control groups)是Linux内核提供的一种机制,这种机制可以根据特定的行为,把一系列系统任务及其子任务整合(或分隔)到按资源划分等级的不同组内,从而为系统资源管理提供一个统一的框架。通俗的来说,cgroups可以限制、记录、隔离进程组所使用的物理资源(包括:CPU、memory、IO等),为容器实现虚拟化提供了基本保证,是构建Docker等一系列虚拟化管理工具的基石。 实现cgroups的主要目的是为不同用户层面的资源管理,提供一个统一化的接口。从单个进程的资源控制到操作系统层面的虚拟化。 ## Cgroups提供了以下四大功能: • 资源限制(Resource Limitation):cgroups可以对进程组使用的资源总额进行限制。如设定应用运行时使用内存的上限,一旦超过这个配额就发出OOM(Out of Memory)。 • 优先级分配(Prioritization):通过分配的CPU时间片数量及硬盘IO带宽大小,实际上就相当于控制了进程运行的优先级。 • 资源统计(Accounting): cgroups可以统计系统的资源使用量,如CPU使用时长、内存用量等等,这个功能非常适用于计费。 • 进程控制(Control):cgroups可以对进程组执行挂起、恢复等操作。 3、Docker LXC 之 Namespace 访问隔离 Namespace又称为命名空间,它主要做访问隔离。其原理是针对一类资源进行抽象,并将其封装在一起提供给一个容器使用,对于这类资源,因为每个容器都有自己的抽象,而他们彼此之间是不可见的,所以就可以做到访问隔离。 ## Linux提供如下Namespace: Namespace Constant Isolates • Cgroup CLONE_NEWCGROUP Cgroup root directory • 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 • User CLONE_NEWUSER User and group IDs • UTS CLONE_NEWUTS Hostname and NIS domain name 以上Namespace分别对进程的 Cgroup root、进程间通信、网络、文件系统挂载点、进程ID、用户和组、主机名域名等进行隔离。 ## 创建容器(进程)主要用到三个系统调用: • clone() – 实现线程的系统调用,用来创建一个新的进程,并可以通过上述参数达到隔离 • unshare() – 使某进程脱离某个namespace • setns() – 把某进程加入到某个namespace 4、Docker LXC 之 Chroot 文件隔离 Chroot 全称是change to root : 其中root 是根目录的意思,也就是改变(linux 根目录是/,也可以理解为设置)一个程序运行时参考的根目录的位置。 ## 根目录的参考 linux 系统(原始的方案) | 引入chroot 机制 / /lxc /usr /lxc/usr /bin /lxc/bin /sys /lxc/sys 如上我们一旦使用了chroot ,用户的中心就不是linux 系统的根目录,而是我们指定的/lxc (这个目录可以任意指定),所以chroot 确实可以修改根目录。 ## chroot 机制的意义: • 增强系统的安全行 • 指定程序访问的根目录,防止用攻击者可以通过程序的漏洞获取其他目录的读写权限;比如/etc/passwd 比如/ 下所有的权限 ## chroot 机制在虚拟化中的作用: chroot 机制因为安全问题才被引入的,但是在LXC 中却启动了举足轻重的作用,因为chroot 机制可以指定虚拟根目录,让不同的容器在不同的根目录下工作。 • 不同的容器进程参考的根目录不同,相互直接不影响 • 不同的容器因为共享linux 底层的文件系统,所以容器集成os的功能,实现轻量级! 三、Docker 虚拟化特点 ## Docker 虚拟化跟传统虚拟化相比,有发下优点: • 操作启动快,运行时的性能可以获取极大提升,管理操作(启动、停止、开始、重启等)都是以秒或毫秒为单位的; • 轻量级虚拟化,用户会拥有足够的“操作系统”,仅需添加或减少镜像即可,单台服务器上可以部署100~1000个containers 容器,而传统虚拟化能虚拟10~20个虚拟机就非常不错了; • 开源免费,成本低,由现代Linux 内核支持并驱动; • 前景及云支持,正在越来越受欢迎,各大主流公司都在推动Docker 的快速发展,性能有很大的优势; • 更快速地交付和部署,Docker 在整个开发周期都可以完美的辅助用户实现快速交付; • 更快速地创建及迭代,Docker 能够快速迭代应用程序,并让整个过程全程可见,使团队中的其他成员更容易理解应用程序是如何创建和工作的; • 高效的部署和扩容,Docker 容器几乎可以在任意的平台上运行,包括物理机、虚拟机、公有云、私有云、个人电脑、服务器等; • 更简单的管理,使用Docker,只需要小小的修改,就可以替代以往大量的更新工作,所有的修改都以增量的方式被分发和更新,从而实现自动化并且高效的管理。 四、Docker 虚拟化原理 Docker 虚拟化中啊核心的部分分为Docker 引擎,Docker 引擎是一个C/S(client/server)结构的应用。 Docker server 是一个常驻进程,rest API实现了client 和server 间的交互协议,CLI 实现容器和镜像的管理,为用户提供统一的操作界面。Docker 使用C/S架构,client 通过接口与server 进程通信实现容器的构建、运行和发布,client和server可以运行在同一台集群,也可以通过跨主机实现远程通信。 完整的Docker 镜像可以支撑一个Docker 容器的运行,在Dock er 容器运行过程中主要提供文件系统数据支撑。Docker 镜像作为Docker 中最基本的概念,有以下特性: • 镜像分层,每个镜像都由一个或多个镜像层组成; • 可通过在某个镜像上加上一定的镜像层得到新镜像(此过程可通过编写DockerFile或基于容器commit实现); • 每个镜像层拥有唯一镜像ID; • 镜像在存储和使用时共享相同的镜像层(根据ID),所以在pull镜像时,已有的镜像层会自动跳过下载; • 每个镜像层都是只读的,即使启动成容器,也无法对其真正的修改,修改只会作用于最上层的容器层。 Docker 容器,可以理解为一个或多个运行进程,而这些运行进程将占有相应的内存、相应的CPU计算资源、相应的虚拟网络设备以及相应的文件系统资源。而Docker 容器所占用的文件系统资源,则通过Docker 镜像的镜像层文件来提供。 基于每个镜像的json文件,Docker 可以通过解析Docker 镜像的json文件,获知应该在这这个镜像之上运行什么样的进程,应该为进程配置怎样的环境变量,Docker 守护进程实现了静态向动态的转变。 五、Docker 安装配置 CentOS 6.X系统安装Docker 软件,首先要关闭SELinux,然后需要安装相应的epel源,安装代码如下: # sed -i '/SELINUX/s/enforcing/disable/g' /etc/selinux/config # setenfoce 0 # wget http://ftp.riken.jp/Linux/fedora/epel/6/x86_64/epel-release-6-8.noarch.rpm # rpm -ivh epel-release-6-8.noarch.rpm # yum install lxc libcgroup device-mapper-event-libs # yum install docker -io # yum install device-mapper* -y Docker 安装完毕后,启动docker 进程/etc/init.d/docker start,并且查看docker 进程:# ps -ef|grep docker CentOS 7.X系统安装Docker 软件,首先要关闭SELinux,然后需要安装相应的epel源,安装代码如下: # sed -i '/SELINUX/s/enforcing/disable/g' /etc/selinux/config # setenfoce 0 • 卸载旧版本 较旧版本的Docker被称为docker或docker-engine。如果已安装这些,请卸载它们以及相关的依赖项。 # yum remove docker docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-selinux \ docker-engine-selinux \ docker-engine • 设置官方存储库安装 # yum install -y yum-utils device-mapper-persistent-data lvm2 # yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo • 启用边缘和测试存储库(默认是禁用的) # yum-config-manager --enable docker-ce-edge # yum-config-manager --enable docker-ce-test # yum-config-manager --disable docker-ce-edge #使用disable禁用 • 安装DOCKER CE 社区版(CE) # yum install docker-ce • 列出仓库中可用的版本 # yum list docker-ce --showduplicates | sort -r docker-ce.x86_64 18.03.1.ce-1.el7.centos docker-ce-stable docker-ce.x86_64 18.03.0.ce-1.el7.centos docker-ce-stable docker-ce.x86_64 17.12.1.ce-1.el7.centos docker-ce-stable docker-ce.x86_64 17.12.0.ce-1.el7.centos docker-ce-stable docker-ce.x86_64 17.09.1.ce-1.el7.centos docker-ce-stable docker-ce.x86_64 17.09.0.ce-1.el7.centos docker-ce-stable • 安装特定的版本example: docker-ce-18.03.1.ce # yum install docker-ce-<VERSION STRING> • 启动并测试 # systemctl start docker # docker run hello-world • 升级DOCKER CE 要升级Docker CE,请按照仓库版本列表选择要安装的新版本。 • 从包安装 如果无法使用Docker的存储库来安装Docker,则可以下载.rpm适用于您的发行版的 文件并手动安装。每次要升级Docker时都需要下载新文件。 转到 https://download.docker.com/linux/centos/7/x86_64/stable/Packages/ 并下载.rpm要安装的Docker版本的文件。 注意:要安装边缘 包,stable请将上述URL中的单词更改 为edge。 # yum install docker-ce-18.03.1.ce-1.el7.centos.x86_64.rpm • 使用脚本安装 # curl -fsSL https://get.docker.com -o get-docker.sh # /bin/sh get-docker.sh 六、Docker 必备命令 对Docker 技术的深入学习,需要构建Docker 基础环境,熟练使用Docker 各种语法命令,要模拟Docker 虚拟化环境,需下载Docker 镜像,通过命令在宿主机服务器上直接下载Docker 公共仓库的镜像,具体步骤如下: 公共仓库Nginx 和CentOS 镜像下载以及本地导入CentOS 镜像,执行如下命令: # docker pull nginx #Docker 下载Nginx镜像 # docker pull centos #Docker 下载CentOS镜像 # cat centos68.tar |docker import - centos #本地导入CentOS镜像 对Docker 的管理除了可以下载镜像、导入镜像之外,还要掌握如下命令: • docker version #查看Docker 版本 • docker search CentOS #搜索可用的Docker 镜像 • docker images #查看当前Docker 所有镜像 • docker pull CentOS #下载CentOS镜像 • cat xxx|docker import - newname #本地导入Docker 镜像 • docker export container_id >cenos6.tar #Docker 导出镜像 • docker run CentOS echo "hello world" #在Docker 容器中运行hello world • docker run CentOS yum install ntpdate #在容器中安装ntpdate的程序 • docker ps -l #获得最后一个容器的ID • docker ps -a #查看所有的容器 • docker commit 87e2313132 CentOS:v1 #提交刚修改的容器 • docker run -i -t -d CentOS /bin/bash #启动Docker 镜像,-d 表示后台启动,-t 表示打开终端,-i 表示交互输入 • docker stop id #关闭Docker 容器 • docker start id #启动Docker 容器 • docker rm id #删除Docker 容器 • docker rmi images #删除Docker 镜像 • docker run -d -p 80:80 -p 8022:22 CentOS:v2 #-p 表示指定Docker 容器端口映射,前面是宿主主机本地端口,后面是Docker 窗口中端口。 • docker exec -it docker_id /bin/bash #进入Docker 容器shell终端 • docker exec docker_id df -h #查看Docker 容器内部磁盘分区 七、Docker 网络详解 1、基于Docker run 创建Docker 容器时,可以使用--net 选项指定容器的网络模式,Docker 默认有以下4种模式: • host 模式,使用--net=host 指定; • container 模式,使用--net=container:NAME_OR_ID 指定; • none 模式,使用--net=none 指定; • bridge 模式,使用--net=bridge 指定,默认设置 2、Docker 4 种网络模式详解如下: • host 模式详解: 基于host模式,容器将不会获得一个独立的network namespace,而是与宿主机共用一个network namespace,容器将不会虚拟出自己的网卡、配置自己的IP等,而是使用宿主机的IP和端口。 • container 模式详解: 理解host 模式后,container 模式也非常好理解,container 模式指定新创建的容器和已经存在的一个容器共享一个network namespace,而不是和宿主机共享共享。即新创建的容器不会创建自己的网卡、配置自己的IP,而是和一个指定的容器共享IP、端口范围等。同样两个容器除了网络方面相同之外,其他的如文件系统、进程列表等还是隔离的。 • none 模式详解: Docker 容器拥有自己的network namespace,但是并不为Docker 容器进行任何网络配置。该Docker 容器没有网卡、IP、路由等信息,需要手工为Docker 容器添加网卡、配置IP等,典型pipework 工具为Docker 容器指定IP等信息。 • bridge 桥接模式详解: bridge 模式是Docker 默认的网络模式,该模式会为每一个容器分配network namespace、设置IP、路由等配置,默认会将Docker 容器连接到一个虚拟网桥交换机docker0上。 3、默认使用Docker 创建Docker 容器网络为bridge 模式,以下为创建Docker bridge创建过程: • 启动Docker 容器,首先会在Docker 宿主机上创建一对虚拟网卡 veth pair 设备,veth 设备总是成对出现的,组成了一条数据通道,数据从一端设备进入,就会从另一端设备出来,veth 设备常用来连接两个网络设备; • Docker 将veth pair 设备的一端放在新创建的容器中。并命名为eth0,然后将另一端放在宿主机中,以vethxxx 这样类似的名字命名,并将这个网络设备加入到docker0网桥中; • 从docker0 子网中分配一个IP给容器使用,并设置docker0 IP地址为窗口默认网关; • 此时,容器IP与宿主机能够通信,宿主机也可访问容器中的IP地址,在bridge 模式下,连在同一网桥上的容器之间可以相互通信,同时容器也可以访问外网,但是其他宿主机不能访问Docker 容器IP,需要通过NAT 将容器IP的port 映射为宿主机的IP和port,方可使用。 八、Docker 桥接模式 Docker 容器默认使用docker0桥接网络,IP地址会自动分配,每个容器都是连接到docker0网桥上的。如果想让容器与宿主机同一网段的其他宿主机之间能访问,须在启动Docker 的时候将Docker 容器的某个端口映射到该宿主机的端口,其他宿主机连接Docker 宿主机的IP和port 即可。 在生产环境中可以自定义Docker 桥接网卡,好处是可以设置Docker 容器的IP与宿主机同网段,无须NAT映射端口访问,更加方便、快捷,同时也可以基于pipework脚本为Docker 容器指定静态IP地址,以下为Docker 自定义桥接网络的配置方法,执行代码如下: # yum install bridge-utils #安装bridge相关库支持 # /etc/init.d/docker stop #停止Docker 服务 # ifconfig docker0 down #关掉docker0 # brctl delbr docker0 #删除docker0 # brctl addbr br0 #创建br0网桥 # ip link set dev br0 up #开户br0网桥 # ip addr add 192.168.1.6/24 dev br0 #为br0分配物理网络中的IP地址 # ip addr del 192.168.1.6/24 /dev ens0 #将宿主机网卡的IP清空 # brctl addif br0 eht0 #将宿主机eth0网卡挂到br0上 # ip route del default #删除原路由 # ip route add default via 192.168.1.6 dev br0 #为br0设置路由 如果Docker 宿主机操作系统为CentOS 6.X,Docker 启用br0设置如下: # vim /etc/sysconfig/docker other_args="-b=br0" 上述配置方法比较烦琐,生产环境建议直接通过创建网桥br0配置文件实现桥接,在/etc/sysconfig/network-scripts/下,修改原ifcfg-eth0网卡配置,同时增加ifcfg-eth0桥接网卡配置,操作步骤如下: 1、vim ifcfg-eth0 内容修改如下: DEVICE=eth0 BOOTPROTO=none NM_CONTROLLED=no ONBOOT=yes TYPE=Ethernet BRIDGE="br0" IPADDR=192.168.1.6 NETMASK=255.255.255.0 GATEWAY=192.168.1.254 USERCTL=no 2、vim ifcfg-br0 内容修改如下: DEVICE="br0" BOOTPROTO=none IPV6INIT=no NM_CONTROLLED=no ONBOOT=yes TYPE="Bridge" IPADDR=192.168.1.6 NETMASK=255.255.255.0 GATEWAY=192.168.1.254 USERCTL=no 3、Docker 桥接网卡配置完毕,直接重启network 服务即可。 # /etc/init.d/network restart 4、修改Docker 桥接网卡为br0。如果Docker 宿主机操作系统为CentOS 7.X,Docker 启用br0 设置如下,然后重启Docker 服务即可。 # vim /etc/sysconfig/docker-network DOCKER_NETWORK_OPTIONS="-b=br0" 启动新的Docker 容器,使用命令docker attach容器ID或docker exec -it 容器ID /bin/bash 进入容器,会自动分配 192.168.1.x 网段的IP地址。 通过配置br0桥接网卡,快速实现Docker 容器快速获取动态IP地址,生产环境服务器的IP均为静态IP,基于pipework工具为Docker 容器指定静态IP地址,以下为pipework工具配置Docker 容器静态IP地址的方法,通过pipework指定的静态IP,当容器重启之后,静态IP会丢失,所以启动容器之前需重新绑定该IP,也可以通过shell脚本自动配置IP,代码如下: #安装pipework工具 git clone https://github.com/jpetazzo/pipework cp ~/pipework/pipework /usr/local/bin/ docker run -itd --net=none --name=lamp2 CentOS 7 /bin/bash #基于pipework 设置Docker 容器IP为192.168.1.11,网关为192.168.1.6,Docker 容器IP子网掩码为255.255.255.0 pipework br0 lamp2 192.168.1.11/24@192.168.1.6 查看Docker 容器IP地址,执行如下代码: # docker exec lamp2 ifconfig 九、Docker 桥接模式 DockFile 是一种能被Docker 程序解释的脚本,DockerFile 由多条指令组成,每条指令对应Linux 系统中不同的命令,基于DockerFile 可以自定义创建生产环境所需的Docker 镜像,通过镜像可以启动所需的Docker 容器。 Docker 程序将这些DockerFile 指令翻译为真正的Linux 命令,DockerFile 有特定的书写格式和支持的命令,Docker 程序解决这些命令间的依赖关系,类似于Linux系统中编译软件所使用的MakeFile 文件。 Docker 程序可以读取DockerFile 文件,根据指令生成定制的image,需要定制自己额外的需求时,只需在DockerFile 上添加或修改指令,重新生成image即可,省去了敲命令的麻烦,以下为DockerFile 镜像制作常用的命令详解: FROM<image>:<tag> FROM指令表示指定一个基本的镜像源,或从公共库拉取一个镜像源,DockerFile文件第一行必须指定FROM基础镜像源 MAINTAINER 设置DockerFile 编写人或维护者的信息 LABEL<key>=<value> 设置标签、采用键值对的形式 RUN<command> 核心命令,表示运行的Linux 指令,每条RUN指令在当前基础镜像上执行,并且提交成为新的镜像 EXPOSE<port>[<port>] 用来指定Docker 容器中监听的端口,用于外界宿主机互联访问,启动Docker 时,可以通过-P,主机会自动分配一个端口号转发到指定的端口 ENV<key>=<value> 设置环境变量,执行RUN指令及Docker 启动时被引用 WORKDIR /path/to/workdir 设置工作目录,执行RUN、ADD、COPY、ENV指令时的基础路径 COPY<src><dest>和ADD<src><dest> Linux 系统增加及复制文件,ADD在和COPY相同的基础上,ADD允许<src>是一个URL,同时ADD的<src>是一个压缩格式文档,<src>将会解压缩复制 CMD和ENTRYPOINT 配置Docker 容器启动后执行的命令,每个DockerFile 至少指定一个CMD命令或ENTRYPOINT,两者都可以指定shell或exec函数调用的方式执行命令,默认DockerFile run启动镜像之后便会退出容器,需要一个长时间运行的命令,使得容器一直执行 • CMD和ENTRYPOINT的详解如下: CMD ["executable","param1","param2"] 运行一个可执行的文件并提供参数 CMD ["param1","param2"] 为ENTRYPOINT指定参数 CMD command param1 param2 以/bin/sh -c 的方法执行的命令 ENTRYPOINT ["executable","param1","param2"] 首选执行形式 ENTRYPOINT command param1 param2 以/bin/sh -c 的方法执行的命令 • CMD和ENTRYPOINT的区别如下: 每个 DockerFile 只能有一个CMD/ENTRYPOINT指令,超过一个CMD只有最后一个生效; CMD在运行时会被Docker run command 指定命令覆盖,而ENTRYPOINT 不会被运行时Docker run command 覆盖; DockeFile 中同时设置CMD和EINTRYPOINT,Docker 在build 过程中会将CMD中指定的内容作为ENTRYPOINT 的参数; 如果Docker 启动需运行多个启动命令,彼此之间可以使用&&分开,最后一个命令必须为无限运行的命令,否则启动的容器将会被退出。 VOLUME [DIR] 设置本地挂载目录,用于存放数据库和需要保持的数据 USER daemon 指定Docker 运行时的用户名或UID,后续的RUN也会使用指定用户 ONBUILD [INSTRUCTION] 配置当前所创建的镜像作为其他新创建镜像的基础镜像时,所执行的操作指令 十、DockerFile 企事业案例 (一)基于DockerFile 相关指令,可以在Docker 宿主机上编写DockerFile 文件,本案例为实现Docker 容器运行,并对外开启22端口,DockerFile 代码如下: #设置基本的镜像,后续命令都以这个镜像为基础 FROM CentOS_lamp:v1 #作者信息 MAINTAINER TANG.COM #RUN 命令会在上面指定的镜像里执行任何命令 RUN yum install passwd openssl openssh-server -y RUN echo '123456'|passwd --stdin root RUN sed -i '/^session\s\+required\s\+pam_loginuid.so/s/^/#/' /etc/pam.d/sshd RUN mkdir -p /root/.ssh&&chown root.root /root&&chmod 700 /root/.ssh RUN mkdir /var/run/sshd #暴露ssh端口22 EXPOSE 22 #设定运行镜像时的默认命令并且打印Docker IP地址,以daemon 方式启动sshd CMD ip addr ls eth0 |awk '{print $2}' |egrep -o '([0-9]+\.){3}[0-9]+';/usr/sbin/sshd -D (二)基于DockerFile 相关指令,可以在Docker 宿主机上编写DockerFile 文件,本案例为实现Docker 容器运行,并对外开启80端口,DockerFile 代码如下: #设置基本的镜像,后续命令都以这个镜像为基础 FROM CentOS_lamp:v1 #作者信息 MAINTAINER TANG.COM #RUN 命令会在上面指定的镜像里执行任何命令 RUN yum install pcre-devel -y RUN yum install httpd httpd-devel -y RUN echo "<h1>The Test Page TANG</h1>" >>/var/www/html/index.html #暴露ssh端口22 EXPOSE 22 #启动httpd CMD ["/usr/sbin/apachectl","-D","FOREGROUND"] (三)基于DockerFile 相关指令,可以在Docker 宿主机上编写DockerFile 文件,本案例为实现Docker 容器运行,并对外开户3306端口,DockerFile 代码如下: FROM CentOS:v1 RUN groupadd -r mysql && useradd -r -g msyql mysql RUN install -y gcc zlib-devel gd-devel ENV MSYQL_MAJOR 5.6 ENV MSYQL_VERSION 5.6.20 RUN && curl -SL "http://dev.mysql.com/get/Downloads/MySQL-$MYSQL_MAJOR/mysql-$MYSQL_VERSION-linux-glibc2.5-x86_64.tar.gz" -o msyql.tar.gz\ && curl -SL "http://mysql.he.net/Downloads/MySQL-$MYSQL_MAJOR/mysql-$MYSQL_VERSION-linux-glibc2.5-x86_64.tar.gz.asc" -o msyql.tar.gz.asc\ && mkdir /usr/local/mysql \ && tar -xzf msyql.tar.gz -C /usr/local/msyql \ && rm mysql.tar.gz* \ ENV PATH $PATH:/usr/local/mysql/bin:/usr/local/mysql/scripts WORKDIR /usr/local/mysql VOLUME /var/lib/mysql EXPOSE 3306 CMD ["mysqld","--datadir=/var/lib/mysql","--user=mysql"] (五)基于DockerFile 相关指令,可以在Docker 宿主机上编写DockerFile 文件,本案例为实现Docker 容器运行,并对外开启8080端口,DockerFile 代码如下: FROM CentOS:v1 #设置DockerFile 运行工作目录 WORKDIR /tmp #安装JAVA JDK RUN wget --no-cookies --no-check-certificate --header "Cookie:gpw_e24=http%3a%2f%2fwww.oracle.com%2ftechnetwork%2fjava%2fjavase%2fdownloads%2fjdk7-downloads-1880260.html;oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/7u79-b15/jdk-7u79-linux-x64.tar.gz RUN tar -zxf jdk-7u70-linux-x64.tar.gz RUN mkdir -p /usr/java/ RUN mv jdk1.7.0_79 /usr/java/ #配置环境变量 ENV JAVA_HOME /usr/java/jdk1.7.0_79/ ENV JRE_HOME $JAVA_HOME/jre ENV CLASSPATH.:$JAVA_HOME/lib:$JRE_HOME/lib ENV PATH $PATH:$JAVA_HOME/bin #安装配置tomcat服务 RUN wget http://mirror.bit.edu.cn/apache/tomcat/tomcat-7/v7.0.62/bin/apache-tomcat-7.0.62.tar.gz RUN tar xvf apache-tomcat-7.0.62.tar.gz RUN mv apache-tomcat-7.0.62 /usr/local/tomcat/ #配置tomcat 环境变量 ENV CATALINA_HOME /usr/local/tomcat/ EXPOSE 8080 #设置tomcat 自启动 CMD ["/usr/local/tomcat/bin/catalina.sh","run"] 十一、Docker 磁盘扩容 device mapper是Linux2.6内核中提供的一种从逻辑设备到物理设备的映射框架机制,device mapper driver默认会创建一个100GB的存储文件,主要用于存储镜像和容器,每一个容器都被限制在10GB大小的卷内,也可以基于loopback 自动创建稀疏文件,具体为用/var/lib/docker/devicemapper/devicemapper下的data和metadata实现动态磁盘扩容,默认创建的100GB存储总空间和Docker 容器10GB是无法满足生产环境应用的,需要扩大Docker 总容量和Docker 容器的rootfs 根系统大小。 Docker 服务在启动的时候可以配置device mapper 的启动参数,docker -d --storage-opt dm.foo=bar,常见参数如下: dm.basesize 默认为10GB,限制容器和镜像的大小 dm.loopdatasize 存储池大小。默认为100GB dm.datadev 存储池设备,/var/lib/docker/devicemapper/devicemapper/data dm.loopmetadatasize 元数据大小,默认为2GB dm.metadatasize 无数据设备,/var/lib/docker/devicemapper/devicemapper/metadata dm.fs 文件系统,置认为ext4 dm.blocksize blocksize 默认为64KB dm.blkdiscard 默认为true 将Docker 置信存储池从100GB扩大到2TB,存储池元数据从2GB扩大到10GB,执行命令如下: rm -rf /var/lib/docker/devicemapper/devicemapper mkdir -p /var/lib/docker/devicemapper/devicemapper dd if=/dev/zero of=/var/lib/docker/devicemapper/devicemapper/data bs=1G count=0 seek=2000 dd if=/dev/zero of=/var/lib/docker/devicemapper/devicemapper/metadata bs=1G count=0 seek=10 还可以通过配置文件直接添加以下代码实现将Docker 默认存储池从100GB扩大到2TB,存储池元数据从2GB扩大到10GB,修改配置文件/etc/sysconfig/docker-storage,加入如下代码: DOCKER_STORAGE_OPTIONS="--storage-opt dm.loopdatasize=2000G --storage -opt dm.loopmetadatasize=10G --storage-opt dm.fs=ext4" 上述配置完毕后,重启Docker 服务即可,新生成的Docker 容器磁盘大小即可生效,如果不在配置文件中指定,还可以基于以下命令直接启动,生产环境推荐修改配置文件,而不推荐命令行方式直接启动,执行命令如下: docker -d --storage-opt dm.loopdatasize=2000G --storage-opt dm.loopmetadatasize=10G --storage-opt dm.fs=ext4 以上方法只适用于新容器生成,并且修改后需要重启Docker ,无法做到动态地给正运行的容器指定大小,基于现有容器在线扩容,宿主机文件系统类型支持ext2、ext3、ext4,不支持XFS。 Docker 在线扩容方法: • 查看原容器的磁盘空间大小 # df -h • 查看Docker 存储device mapper 设备名称 # ls -l /dav/mapper/docker-* • 查看Docker mapper 卷信息表查看该设备所占用的多少扇区 # dmsetup table docker-****************************** • 计算扩容大小 扩容大小是以扇区为单位的,例如10GB有扇区近 20971520个左右,装空间扩容为15GB,计算15GB的空间所需扇区的大小: echo $((15*1024*1024*1024/512)) 31457280 然后修改Dokcer 容器卷信息表、激活并且验证,使用echo 命令将新的扇区大小写入, 注意只是改变20971520 的数字为31457280 ,其他数字不变,通过命令dmsetup resume 将修改后的容器文件激活,通过命令dmsetup table 查看最新Docker 扇区信息。 # echo 0 31457280 thin 253:0 30 |dmsetup load docker-***************************** # dmsetup resume docker-****************************** # dmsetup table docker-******************************* • 修改文件系统大小,命令为resize2fs # resize2fs /dev/mapper/docker-****************************** • 验证Docker rootfs 磁盘大小 # df -h 通过上述步骤成功地将Docker 容器的10GB空间扩容为15GB,还可以将上述步骤写成shell脚本,基于脚本参数快速扩容。给Docker 磁盘扩容除了采用上述方法外,还可以使用挂载目录方法,基于-v参数,在启动Docker 容器时指定。 十二、Docker 构建私有仓库 Docker 镜像默认存放在仓库中,Docker 仓库分为公共仓库和私有仓库,随着公司业务的发展,Docker 镜像的种类也非常繁多,为了统一管理,可以基于registry 搭建本地私有仓库。 1、使用Docker 私有仓库有以下优点: • 节省网络带宽,针对每个镜像不用去Docker 官网仓库下载; • Docker 镜像从本地私有仓库中下载; • 构建公司内部私有仓库,方便各部门使用,服务器管理更加统一; • 可以基于GIT或SVN、Jenkins更新本地Docker 私有仓库镜像版本。 2、使用Docker registry 构建本地私有仓库的方法及步骤: • 下载Docker registry 镜像 # docker pull registry • 启动私有仓库容器 # mkdir -p /data/registry/ # docker run -itd -p 5000:5000 -v /data/registry:/tmp/registry docker.io/registry 默认情况下,会将仓库存放于容器内的/tmp/registry目录下。这样如果容器被删除,则存放于容器中的镜像也会丢失,所以一般情况下会指定本地/data/registry 目录挂载到容器内的/tmp/registry下。 • 上传镜像至本地私有仓库。客户端上传镜像至本地私有仓库,以busybox 镜像为例,将busybox上传至私有仓库服务器,命名如下: # docker pull busybox # docker tag busybox 192.168.1.123:5000/busybox # docker push 192.168.1.123:5000/busybox • 检测本地私有仓库: # curl -XGET http://192.168.1.123:5000/v2/_catalog # curl -XGET http://192.168.1.123:5000/v2/busybox/tags/list • 客户端本地私有仓库。在客户端Docker 配置文件/etc/sysconfig/docker 中添加以下代码,同时重启Docker 服务,获取本地私有仓库。 OPTIONS='--selinux-enabled --log-driver=journald --signature-verification=false --insecure-registry 192.168.1.123:5000' ADD_REGISTRY='--add-registry 192.168.1.123:5000' 至此,Dokcer 本地私有仓库部署完毕,可以向仓库中添加、更新Docker 镜像,或查看、删除Docker 仓库相关的镜像,操作命令如下: # curl -XGET http://192.168.1.123:5000/v2/_catalog # curl -XGET http://192.168.1.123:5000/v2/image_name/tags/list # curl -X DELETE http://192.168.1.123:5000/v1/repositories/镜像名称 #v2 版本,官网不建议删除私有仓库中的镜像,可以基于delete-docker-registry-image工具 #删除Dokcer 镜像 # curl https://raw.githubusercontent.com/burnettk/delete-docker-registry-image/master/delete_docker_registry_image.py|sudo tee /usr/local/bin/delete_docker_registry_image >/dev/null # chmod a+x /usr/local/bin/delete_docker_registry_image # export REGISTRY_DATA_DIR=/data/registry/v2 # delete_docker_registry_image --image centos:v1 十三、Docker 自动化部署(一) 想批量应用于生产环境,需要编写能够实现自动安装并配置Docker 虚拟化及桥接网络的脚本,同时使用pipework 这个软件来配置容器IP,能够实现容器简单的管理,以下为CentOS 6.X Linux 系统一键安装、配置、管理Docker 的shell 脚本,脚本代码如下: #!/bin/bash #auto install docker and create VM #By tang.com 2018 #Define PATH Varablies IPADDR=`ifconfig|grep "Bcast"|awk '{print $2}'|cut -d: -f2|grep "192.168"|head -1` GATEWAY=`route -n|grep "UG"|awk '{print $2}'|grep "192.168"|head -1` DOCKER_IPADDR=$1 IPADDR_NET=`ifconfig|grep "Bcast"|awk '{print $2}'|cut -d: -f2|grep "192.168"|head -1|awk -F. '{print $1"."$2"."$3".""xxx"}'` NETWORK=( HWADDR=`ifconfig eht0 |egrep "HWaddr|Bcast"|tr "\n" " "|awk '{print $5,$7,$NF}'|sed -e 's/addr://g' -e 's/Mask://g'|awk '{print $1}'` IPADDR=`ifconfig eht0 |egrep "HWaddr|Bcast"|tr "\n" " "|awk '{print $5,$7,$NF}'|sed -e 's/addr://g' -e 's/Mask://g'|awk '{print $2}'` NETMASK=`ifconfig eht0 |egrep "HWaddr|Bcast"|tr "\n" " "|awk '{print $5,$7,$NF}'|sed -e 's/addr://g' -e 's/Mask://g'|awk '{print $3}'` GATEWAY=`route -n|grep "UG"|awk '{print #2}'` ) if [ -z "$1" -o -z "$2" -o -z "$3" -o -z "$4" ];then echo -e "\033[32m------------------------------------\033[0m" echo -e "\033[32mPlease exec $0 IPADDR CPU(C) MEM(G) DISK(G),example $0 $IPADDR_NET 16 32 50\033[0m" exit 0 fi CPU=`expr $2 -1` if [ ! -e /usr/bin/bc ];then yum install bc -y >>/dev/null 2>&1 fi MEM_F=`echo $3 \*1024|bc` MEM=`printf "%.0f\n" $MEM_F` DISK=$4 USER=$5 REMARK=$6 ping $DOCKER_IPADDR -c 1 >>/dev/null 2>&1 if [ $? -eq 0 ];then echo -e "\033[32m-------------------\033[0m" echo -e "\033[32mThe IP address to be used,Please change other IP,exit.\033[0m" exit 0 fi if [ ! -e /etc/init.d/docker ];then rmp -ivh http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm yum install docker-io -y yum install device-mapper* -y /etc/init.d/docker start if [ $? -ne 0 ];then echo "Docker install error,please check." exit fi fi cd /etc/sysconfig/network-scripts/ mkdir -p /data/backup/`data +%Y%m%d-%H%M` yes|cp ifcfg-eth* /data/backup/`data +%Y%m%d-%H%M`/ if [ -e /etc/sysconfig/network-scripts/ifcfg-br0 ];then echo else cat>ifcfg-eth0<<EOF DEVICE=eth0 BOOTPROTO=none ${NETWORK[0]} NM_CONTROLLED=no ONBOOT=yes TYPE=Ethernet BRIDGE="br0" ${NETWORK[1]} ${NETWORK[2]} ${NETWORK[3]} URERCTL=no EOF cat>ifcfg-br0<<EOF DEVICE="br0" BOOTPROTO=none ${NETWORK[0]} IPV6INIT=no ONBOOT=yes TYPE="Bridge" ${NETWORK[1]} ${NETWORK[2]} ${NETWORK[3]} USERCTL=no EOF /etc/init.d/network restart fi echo "Your can restart Ethernet Service:/etc/init.d/network restart !" echo '---------------------------------------------------------------' cd - #######create docker container service docker status >>/dev/null if [ $? -ne 0 ];then /etc/init.d/docker restart fi NAME="Docker_`echo $DOCKER_IPADDR|awk -F"." '{print $(NF-1)"_"$NF}'`" IMAGES=`docker images|grep -v "REPOSITORY"|grep -v "none"|head -1|awk '{print $1}'` CID=$(docker run -itd --cpuset-cpus=0-$CPU -m ${MEM}m --net=none --name=$NAME $IMAGES /bin/bash) if [ -z $IMAGES ];then echo "Please Download Docker CentOS Images,you can to be use docker search CentOS,and docker pull CentOS6.5-ssh,exit 0" exit 0 fi if [ ! -f /usr/local/bin/pipework ];then you install wget unzip zip -y wget https://github.com/jpetazzo/pipework/archive/master.zip unzip master cp pipework-master/pipework /usr/local/bin/ chmod +x /usr/local/bin/pipework rm -rf master fi ip netns >> /dev/null if [ $? -ne 0 ];then rpm -e iproute --nodeps rpm -ivh https://repos.fedorapeople.org/openstack/EOL/openstack-grizzly/epel-6/iproute-2.6.32-120.el6ost.netns.2.x86_64.rpm fi pipework br0 $NAME $DOCKER_IPADDR/24@ $IPADDR docker ps -a |grep "$NAME" DEV=$(basename $(echo /dev/mapper/docker-*-$CID)) dmsetup table $DEV |sed "s/0 [0-9]* thin/0 $((${DISK}*1024*1024*1024/512)) thin/"|dmsetup load $DEV dmsetup resume $DEV resize2fs /dev/mapper/$DEV docker start $CID docker logs $CID LIST="docker_vmlist.csv" if [ ! -e $LIST ];then echo "编号,容器ID,容器名称,CPU,内存,硬盘,容器IP,宿主机IP,使用人中,备注">$LIST fi ###################################### NUM=`cat docker_vmlist.csv|grep -v CPU|tail -1|awk -F, '{print $1}'` if [[ $NUM -eq "" ]];then NUM="1" else NUM=`expr $NUM + 1` fi ###################################### echo -e "\033[32mCreate virtual client Successfully.\n$NUM `echo $CID|cut -b 1-12` $NAME $2C ${MEM}M ${DISK}G $DOCKER_IPADDR $IPADDR $USER $REMARK\033[0m" if [ -z $USER ];then USER="NULL" REMARK="NULL" fi echo $NUM,`echo $CID|cut -b 1-12`,$NAME,${2}C,${MEM}M,${DISK}G,$DOCKER_IPADDR,$IPADDR,$USER,$REMARK>>$LIST rm -rf docker_vmlist_* iconv -c -f utf-8 -t gb2312 docker_vmlist.csv -o docker_vmlist_`date +%H%M`.csv 十四、Docker 自动化部署(二) 目前越来越多的企业开始使用CentOS7系统,以下为CentOS7.X Linux 系统一键安装、配置Docker的shell脚本: #!/bin/bash #auto install docker and create VM #by tang.com 2018 #Define PATH Varablies IPADDR=`ifconfig|grep -E "\<inet\>"|awk '{print $2}'|grep "192.168"|head -1` GATEWAY=`route -n |grep "UG"|awk '{print $2}'|grep "192.168"|head -1` IPADDR_NET=`ifconfig|grep -E "\<inet\>"|awk '{print $2}'|grep "192.168"|head -1|awk -F. '{print $1"."$2"."$3"."}'` LIST="/root/docker_vmlist.csv" if [ ! -f /usr/sbin/ifconfig ];then yum install net-tools -y fi for i in `seq 1 253`;do ping -c 1 ${IPADDR_NET} ${i};[ $? -ne 0 ]$$DOCKER_IPADDR="${IPADDR_NET}${i}"$$break;done>>/dev/null 2>$1 echo "#########################################" echo -e "Dynamic get docker IP,The Docker IP address\n\n$DOCKER_IPADDR" NETWORK=( HWADDR=`ifconfig eht0 |grep ether|awk '{pirnt $2}'` IPADDR=`ifconfig eht0 |grep -E "\<inet\>"|awk '{print $2}'` NETMASK=`ifconfig eht0 |grep -E "\<inet\>"|awk '{print $4}'` GATEWAY=`route -n|grep "UG"|awk '{print $2}'` ) if [ -z "$1" -o -z "$2" ];then echo -e "\033[32m---------------------------\033[0m" echo -e "\033[32mPlease exec $0 CPU(C) MEM(G),example $0 4 8\033[0m" exit 0 fi #CPU=`expr $2 - 1` if [ ! -e /usr/bin/cb ];then yum install bc -y >>/dev/null 2>&1 fi CPU_ALL=`cat /proc/cpuinfo |grep processor|wc -l` if [ ! -f $LIST ];then CPU_COUNT=$1 CPU_1="0" CPU1=`expr $CPU_1 + 0` CPU2=`expr $CPU1 + $CPU_COUNT - 1` if [ $CPU2 -gt $CPU_ALL ];then echo -e "\033[32mThe System CPU count is $CPU_ALL,not more than it.\033[0m" exit fi else CPU_COUNT=$1 CPU_1=`cat $LIST|tail -1|awk -F"," '{print $4}'|awk -F"-" '{print $2}'` CPU1=`expr $CPU_1 + 1` CPU2=`expr $CPU1 + $CPU_COUNT - 1` if [ $CPU2 -gt $CPU_ALL ];then echo -e "\033[32mThe System CPU count is $CPU_ALL,not more than it.\033[0m" exit fi fi MEM_F=`echo $2 \* 1024|bc` MEM=`printf "%.0f\n" $MEM_F` DISK=20 USER=$3 REMARK=$4 ping $DOCKER_IPADDR -c 1>>/dev/null 2>&1 if [ $? -eq 0 ];then echo -e "\033[32m------------------\033[0m" echo -e "\033[32mThe IP address to be used,Please change other IP,exit.\033[0m" exit 0 fi if [ ! -e /usr/bin/docker ];then yum install docker* device-mapper* lxc -y mkdir -p /export/docker/ cd /var/lib/ ;rm -rf docker;ln -s /export/docker/ . mkdir -p /var/lib/docker/devicemapper/devicemapper dd if=/dev/zero of=/var/lib/docker/devicemapper/devicemapper/data bs=1G count=0 seek=2000 service docker start if [ $? -ne 0 ];then echo "Docker install error,please check." exit fi fi cd /etc/sysconfig/network-scripts/ mkdir -p /data/backup/`date +%Y%m%d-%H%M` yes|cp ifcfg-eth* /data/backup/`data +%Y%m%d-%H%M`/ if [ -e /etc/sysconfig/network-scripts/ifcfg-br0 ];then echo else cat>ifcfg-eth0<<EOF DEVICE=eth0 BOOTPROTO=none ${NETWORK[0]} NM_CONTROLLED=no ONBOOT=yes TYPE=Ethernet BRIDGE="br0" ${NETWORK[1]} ${NETWORK[2]} ${NETWORK[3]} URERCTL=no EOF cat>ifcfg-br0<<EOF DEVICE="br0" BOOTPROTO=none ${NETWORK[0]} IPV6INIT=no NM_CONTROLLED=no ONBOOT=yes TYPE="Bridge" ${NETWORK[1]} ${NETWORK[2]} ${NETWORK[3]} USERCTL=no EOF /etc/init.d/network restart fi echo "Your can restart Ethernet Service:/etc/init.d/network restart !" echo '---------------------------------------------------------------' cd - #######create docker container NAME="Docker_`echo $DOCKER_IPADDR|awk -F"." '{print $(NF-1)"_"$NF}'`" IMAGES=`docker images|grep -v "REPOSITORY"|grep -v "none"|grep "CentOS"|head -1|awk '{print $1}'` if [ -z $IMAGES ];then echo "Please Download Docker CentOS Images,you can to be use docker search CentOS,and docker pull CentOS6.5-ssh,exit 0" if [ ! -f tang_CentOS68.tar ];then echo "Please upload tang_CentOS68.tar for docker server." exit fi cat tang_CentOS68.tar|docker import - tang_CentOS6.8 fi IMAGES=`docker images|grep -v "REPOSITORY"|grep -v "none"|grep "CentOS"|head -1|awk '{print $1}'` CID=$(docker run -itd --privileged --cpuset-cpus=${CPU1}-${CPU2} -m ${MEM}m --net=none --name=$NAME $IMAGES /bin/bash) echo $CID docker ps -a |grep "$NAME" pipework br0 $NAME $DOCKER_IPADDR/24@ $IPADDR docker exec $NAME /etc/init.d/sshd start if [ ! -e $LIST ];then echo "编号,容器ID,容器名称,CPU,内存,硬盘,容器IP,宿主机IP,使用人中,备注">$LIST fi ###################################### NUM=`cat $LIST|grep -v CPU|tail -1|awk -F, '{print $1}'` if [[ $NUM -eq "" ]];then NUM="1" else NUM=`expr $NUM + 1` fi ###################################### echo -e "\033[32mCreate virtual client Successfully.\n$NUM `echo $CID|cut -b 1-12` $NAME $CPU1-$CPU2 ${MEM}M ${DISK}G $DOCKER_IPADDR $IPADDR $USER $REMARK\033[0m" if [ -z $USER ];then USER="NULL" REMARK="NULL" fi echo $NUM,`echo $CID|cut -b 1-12`,$NAME,$CPU1-$CPU2,${MEM}M,${DISK}G,$DOCKER_IPADDR,$IPADDR,$USER,$REMARK>>$LIST rm -rf /root/docker_vmlist_* iconv -c -f utf-8 -t gb2312 $LIST -o /root/docker_vmlist_`date +%H%M`.csv