Docker容器技术
什么是Docker?
Docker是一个开源的引擎,可以轻松的为任何应用创建一个轻量级的、可移植的、自给自足的容器。开发者在笔记本上编译测试通过的容器可以批量地在生产环境中部署,包括VMs(虚拟机)、bare metal、OpenStack 集群和其他的基础应用平台。(来自官方)
Docker的应用场景
1.产品交付:将环境和代码打包成镜像进行交付,交付更方便也更安全。
2.开发环境配置:将环境做成镜像,开发人员直接启动容器载入镜像,开发环境就有了。
3.多版本测试:由于Docker隔离了系统环境,同时容器直接互不影响,可以启动多个容器同时进行多版本的测试。
4.集群环境部署:Docker可以有效保证环境的一致,同时以容器为单位部署服务更加方便,也有利于及时回滚和服务的迁移。
5.架构扩容和自动化运维:Docker快速启动和低资源消耗的优点在弹性云平台和自动化运维方面有很好的应用前景。
Docker的三大组件
1.容器:容器是服务运行的载体。容器为服务虚拟出独立的环境,容器之间互不影响。创建容器需要加载镜像,容器本身也可打包为镜像。
2.镜像:镜像是容器状态的快照。创建一个容器必须要有一个镜像,比如centos镜像、lnmp镜像等。镜像包含完整的应用和环境,启动容器即可运行。
3.仓库:仓库是镜像存放的地点。镜像构建时需要指定仓库,远程仓库可以实现镜像共享,例如DockerHub以及私有化仓库Harbor。
Docker与Openstack的不同点
|
Docker
|
Openstack
|
部署难度
|
非常简单,yum安装docker即可
|
部署复杂,组件较多
|
启动速度
|
秒级
|
分钟级
|
处理性能
|
和物理机几乎一致
|
有一定的性能损失
|
镜像体积
|
MB级别
|
GB级别
|
管理效率
|
管理简单 |
管理复杂,组件相互依赖
|
隔离性
|
不完全隔离
|
彻底隔离
|
进程数目
|
单进程、不建议启动SSH
|
多进程,完整的系统管理
|
网络连接
|
比较弱
|
Neutron组件可以灵活构建网络架构 |
Docker容器和虚拟机原理及性能比较
1.上图分别是虚拟机和Docker容器的实现框架,显然虚拟机比容器要多一个GuestOS操作系统层,于是得到第一个区别:虚拟机调用的是完整的自身的操作系统内核,而Docker容器是直接调用物理机内核。
2.Hypervisor是一个硬件虚拟化平台,它为虚拟机内核提供底层驱动,于是得到第二个区别:虚拟机的资源隔离是利用独立OS和虚拟化硬件来实现,而DockerEngine则是利用物理机内核支持的LXC技术(LinuxContainer),通过namespace、cgroup等机制实现环境和资源的隔离。LXC是内核支持的一种轻量级虚拟化技术,与传统虚拟化技术的最大的区别在于不需要解释机制可直接在物理CPU中运行指令,LXC建立在cgroup基础上,包括cgroup、namespace、chroot、veth以及用户态控制脚本等。cgroup(control groups)是Linux内核提供的一种可以限制、记录进程组所使用物理资源的资源管理机制。namespace(命名空间)是Linux内核提供的一种资源隔离机制,将不同的进程和资源划分在一个局部作用域内,内部的进程和资源只在当前作用域生效,类似于局部变量。
通过上面的比较,我们可以得出结论:
1.Docker直接调用物理机内核,而不需要GuestOS。因此,由于不需要加载内核,创建一个容器只需几秒钟。同时,由于不需要操作系统,docker更加节省资源。
2.Docker直接使用物理机硬件,比虚拟机有更少的抽象层。因此,docker的计算性能和效率几乎和物理机相同。
Docker的优点和缺点
1.优点:Docker和虚拟机一样可以实现资源和环境的隔离,启动速度快,资源消耗低,处理性能强。
2.缺点:Docker的资源隔离不如虚拟机彻底,不能隔绝其他程序占用自己的资源。
Docker不能分辨用户权限,用户只要能执行docker,就可以对容器进行任何操作。
Docker快速入门
cat /etc/redhat-release uname -rm yum install -y docker systemctl start docker ifconfig #docker启动会创建一个虚拟网卡 docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500 inet 172.17.0.1 netmask 255.255.0.0 broadcast 0.0.0.0 ether 02:42:55:0d:41:e1 txqueuelen 0 (Ethernet) RX packets 0 bytes 0 (0.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 0 bytes 0 (0.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 docker images #查看当前镜像 docker search centos #去dockerhub上搜索镜像 INDEX NAME DESCRIPTION STARS OFFICIAL AUTOMATED docker.io docker.io/centos The official build of CentOS. 3625 [OK] docker.io docker.io/ansible/centos7-ansible Ansible on Centos7 100 [OK] docker pull centos #获取镜像 docker load --input centos.tar # 导入镜像 docker save -o centos.tar centos # 导出镜像 docker rmi centos #删除镜像 docker run centos /bin/echo "Hello world" # 创建一个容器并运行一条命令 centos是镜像名称,名称必须在所有选项后面 命令可以有可以没有 docker ps -a # 查看容器的状态 -a表示所有容器,运行和不运行的 默认只显示运行的容器状态 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES d989b65f0158 centos "/bin/echo 'Hello wor" 2 minutes ago Exited (0) About a minute ago pedantic_banach [root@docker ~]# docker run --name mydocker -t -i centos /bin/bash # 表示启动一个容器,名称命名为mydocker -t分配一个伪终端tty -i表示打开标准输入 centos是镜像名称 /bin/bash 是要执行的命令 如果centos不存在会自动去dockerhub上pull下来 #/bin/bash进程必须分配伪终端才能运行,同时打开标准输入后才可以输入命令 [root@7fbe5da0c90f /]# [root@7fbe5da0c90f /]# ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 02:20 ? 00:00:00 /bin/bash # 正常虚拟机PID=1的程序是systemd或者/sbin/init,显然docker容器不是虚拟机 root 16 1 0 02:22 ? 00:00:00 ps -ef [root@7fbe5da0c90f /]# cat /proc/cpuinfo # 这里显示的是物理机的硬件信息,所以docker并没有虚拟出硬件 [root@7fbe5da0c90f /]# exit # 退出容器 [root@docker ~]# docker start mydocker #启动已经创建好的docker容器,run是创建实例,start是容器关闭后再打开 mydocker是容器名称,我们可以通过ID或名称来启动容器 [root@docker ~]# docker --help # 帮助信息 [root@docker ~]# docker attach mydocker # 进入正在运行的容器,需要容器内运行/bin/bash进程 这个命令不靠谱,退出容器后容器就停了 #注:容器内必须有一个前台进程在运行,否则容器就会停止 更好的进入容器方法是nsenter命令,如果没有就yum install -y util-linux [root@docker ~]# docker inspect -f "{{.State.Pid}}" mydocker # 获取容器里面进程在物理机上的PID,注意要先启动容器 9515 [root@docker ~]# ps -ef | grep 9515 root 9515 9505 0 11:22 pts/0 00:00:00 /bin/bash root 9553 8900 0 11:22 pts/1 00:00:00 grep --color=auto 9515 [root@docker ~]# nsenter --help [root@docker ~]# nsenter -t 9515 -m -u -i -n -p # 进入容器 [root@7fbe5da0c90f /]# ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 03:22 ? 00:00:00 /bin/bash root 13 0 0 03:51 ? 00:00:00 -bash # nsenter进入后重新创建了bash root 28 13 0 03:54 ? 00:00:00 ps -ef [root@7fbe5da0c90f /]# exit [root@docker ~]# docker ps # nsenter进入然后退出,只是-bash进程结束,/bin/bash进程不会受到影响,所以容器不会关闭 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7fbe5da0c90f centos "/bin/bash" About an hour ago Up 33 minutes mydocker [root@docker ~]# docker exec mydocker whoami # 往docker容器里面传递命令,容器执行后返回结果 root [root@docker ~]# docker exec -it mydocker /bin/bash # 这样也能进入容器,原理和nsenter不同,但效果相同,但建议使用nsenter,这是进入容器的最佳实践 [root@7fbe5da0c90f /]# ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 03:22 ? 00:00:00 /bin/bash root 50 0 0 04:10 ? 00:00:00 /bin/bash root 62 50 0 04:10 ? 00:00:00 ps -ef [root@docker ~]# docker rm mydocker # 删除容器,rmi是删除镜像 [root@docker ~]# docker rm -f mydocker # 可以删除正在运行的容器 -f force强制删除 [root@docker ~]# docker run --rm centos /bin/echo "hehe" # 启动一个容器,进程运行完毕后自动删除 命令总结: 镜像:docker search # 搜索镜像 docker pull # 获取镜像 docker images # 查看镜像 docker rmi # 删除镜像 docker load --input / docker load < # 导入镜像 docker save -o # 导出镜像 容器:docker run --name -h hostname ... # 创建容器 docker start [ID/NAME] # 启动容器 docker stop [ID/NAME] # 停止容器 docker ps -a # 查看容器 docker exec | docker attach | nsenter # 进入容器 docker rm # 删除容器
Docker网络访问
网络访问:随机映射 docker run -P 指定映射 docker run -p hostPort:containerPort docker run -p ip:hostPort:containerPort docker run -p ip::containerPort # 不加端口就随机 docker run -p hostPort:containerPort:udp docker run -p 81:80 -p 443:443 #Docker网络访问 docker启动时创建了一个桥接网卡docker0 [root@docker ~]# brctl show bridge name bridge id STP enabled interfaces docker0 8000.0242550d41e1 no veth1a9fdb5 [root@docker ~]# docker run -d -P nginx # 随机映射端口 -d 后台启动 5038a3bd7de1dd28b303b8a61df12b4352f3e3c0f779ec148106c8ab652ebd45 [root@docker ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 5038a3bd7de1 nginx "nginx -g 'daemon off" 43 seconds ago Up 39 seconds 0.0.0.0:32768->80/tcp elated_mcclintock #容器的80端口映射到本地的32768端口,我们可以访问看看 http://172.16.1.5:32768 [root@docker ~]# docker run -d -p 10.0.0.5:82:80 --name mynginx nginx 6386aaef223f9965217053b82dacda0527fbf7edba7da90f0db6ba798cf2b276 [root@docker ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 6386aaef223f nginx "nginx -g 'daemon off" About a minute ago Up About a minute 10.0.0.5:82->80/tcp mynginx [root@docker ~]# docker port mynginx # 查看容器端口 80/tcp -> 10.0.0.5:82
编写进入容器的脚本
[root@docker ~]# vim docker_in.sh #进入docker的脚本 #!/bin/bash # Use nsenter to access docker docker_in(){ NAME_ID=$1 PID=$(docker inspect -f "{{.State.Pid}}" $NAME_ID) nsenter -t $PID -m -u -i -n -p } docker_in $1 [root@docker ~]# chmod +x docker_in.sh [root@docker ~]# ./docker_in.sh mydocker
Docker数据管理
容器的镜像是分层的,我们可以在一个镜像基础上再开发,再生成镜像,生成的镜像只保存修改的内容,最终导出的镜像包含基础镜像和添加的镜像两层,下载镜像时也是分层下载。 数据管理:如果要在容器里写数据,为了防止数据写到一半容器故障,我们需要将数据写到物理机上,即需要挂载物理机目录到docker容器里即数据卷技术,同时docker支持容器之间的数据卷共享,其它容器通过访问某个已挂载容器的挂载目录来间接将数据写入物理机,即数据卷容器技术。 数据卷 #挂载目录到容器 [root@docker ~]# docker run -d --name nginx-volumn-test2 -v /data nginx [root@docker ~]# ls /var/lib/docker/volumes/e44afc282ef635422705e42a5292602e4f6ca807a750af2983c86638adbf3751/ _data # 被挂载的源目录,不指定时源目录时docker在物理机上自动创建 不同的容器对应不同的编号 [root@docker ~]# docker run -d --name nginx-volumn-test2 -v /data/docker-volumn-nginx:/data nginx # 指定源目录 也可以挂载文件 src:dst [root@docker ~]# docker run -d --name nginx-volumn-test2 -v /data/docker-volumn-nginx:/data:ro nginx #只读 数据卷容器:挂载了目录(数据卷)的容器就称为数据卷容器。所以可以专门起一个容器用来挂载,其它的容器直接访问这个容器而不再需要挂载 [root@docker volumes]# docker run -it --name volumn-test3 --volumes-from nginx-volumn-test2 centos /bin/bash # --volumes-from 直接访问另一个容器的挂载目录,自己不用挂载。即使test2停了也不影响test3访问
Docker镜像构建
1.手动构建
[root@docker ~]# docker ps -a -q # 获取所有容器的ID e37a40ec1008 2d136a4b9ce8 37a1c1e3e151 b82da6090e37 6386aaef223f 01736bd88a27 5038a3bd7de1 f23d23271be7 7fbe5da0c90f d989b65f0158 [root@docker ~]# docker kill $(docker ps -a -q) # 停止所有容器 [root@docker ~]# docker rm $(docker ps -a -q) # 删除所有容器 [root@docker ~]# docker run --name mynginx -it centos [root@e570416e1c12 /]# wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo [root@e570416e1c12 /]# yum install -y nginx [root@e570416e1c12 /]# vi /etc/nginx/nginx.conf daemon off; # 关闭守护进程,就可以在前台运行 [root@e570416e1c12 /]# exit [root@docker ~]# docker commit -m "my nginx" e570416e1c12 peter/mynginx:v1 # 提交当前容器状态,即创建镜像 跟git很像,git的提交也是对当前目录状态做一个镜像 sha256:390ac6fdc28ae5650673fea9695719c1c3185f7f9cc4eb25233259de158258c0 [root@docker ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE peter/mynginx v1 390ac6fdc28a 3 minutes ago 354.4 MB # peter是仓库名称 mynginx是镜像名称 v1是标签 [root@docker ~]# docker run --name mynginxv1 -d -p 81:80 peter/mynginx:v1 nginx # 从镜像中创建一个容器
2.使用Dockerfile自动构建
[root@docker nginx]# pwd /opt/dockerfile/nginx [root@docker nginx]# vim Dockerfile # 名称是固定的 # This Dockerfile #Base image FROM centos #基础镜像是什么即在什么镜像基础上构建 #Maintainer MAINTAINER Peter.Wang #维护者是谁 #Commands RUN rpm -ivh http://mirrors.aliyun.com/epel/epel-release-latest-7.noarch.rpm RUN yum install -y nginx && yum clean all RUN echo "daemon off;" >> /etc/nginx/nginx.conf ADD index.html /usr/share/nginx/html/index.html # 添加一个文件 EXPOSE 80 # 对外的端口是什么 CMD ["nginx"] # 启动时执行的命令是什么 [root@docker nginx]# echo "nginx in docker hahaha" > index.html # 注意与Dockerfile在同一目录下 [root@docker nginx]# docker build -t mynginx:v2 . # .表示在当前目录下找Dockerfile [root@docker nginx]# docker images [root@docker nginx]# docker run --name mynginxv2 -d -p 82:80 mynginx:v2
Dockerfile的生产实践
#分层设计,先构建系统层的镜像,然后构建运行环境层的镜像,然后构建应用服务层的镜像。docker镜像是分层设计的,并且这种方式使得镜像可以重复使用,耦合度较低 [root@docker docker]# tree . ├── app │ ├── xxx-admin │ └── xxx-api ├── runtime │ ├── java │ ├── php │ └── python └── system ├── centos ├── centos-ssh └── ubuntu [root@docker centos]# wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo [root@docker centos]# cp /etc/yum.repos.d/epel.repo . [root@docker centos]# ls epel.repo
#系统环境centos [root@docker centos]# vim Dockerfile # Docker for CentOS #Base image FROM centos #Maintainer MAINTAINER Peter.Wang xxx@gmail.com #EPEL ADD epel.repo /etc/yum.repos.d/ #Base pkg RUN yum install -y wget mysql-devel supervisor git redia tree net-tools sudo psmisc && yum clean all [root@docker centos]# docker build -t peter/centos:base . [root@docker centos]# docker images
#python环境 [root@docker python]# vim Dockerfile #Base image FROM peter/centos:base #Maintainer MAINTAINER Peter.Wang # Python env RUN yum install -y python-devel python-pip supervisor # Update pip RUN pip install --upgrade pip [root@docker python]# docker build -t peter/python .
#centos-ssh [root@docker centos-ssh]# vim Dockerfile # Docker for CentOS #Base image FROM centos #Maintainer MAINTAINER Peter.Wang xxx@gmail.com #EPEL ADD epel.repo /etc/yum.repos.d/ #Base pkg RUN yum install -y openssh-clients openssl-devel openssh-server wget mysql-devel supervisor git redia tree net-tools sudo psmisc && yum clean all #For SSHD RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key RUN ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key RUN echo "root:123456" | chpasswd [root@docker centos-ssh]# docker build -t peter/centos-ssh .
#python-ssh [root@docker python-ssh]# vim Dockerfile #Base image FROM peter/centos-ssh #Maintainer MAINTAINER Peter.Wang # Python env RUN yum install -y python-devel python-pip supervisor #supervisor是一个进程管理工具,可以同时管理多个进程 #vim /etc/supervisord.conf #[include] #files = supervisord.d/*.ini # 我们只需要在/etc/supervisord.d目录下添加ini文件即可 # Update pip RUN pip install --upgrade pip [root@docker python-ssh]# docker build -t peter/python-ssh .
#shop.api [root@docker shop-api]# vim app.py from flask import Flask app = Flask(__name__) @app.route('/') def hello(): return 'Hello World!' if __name__ == "__main__": app.run(host="0.0.0.0",debug=True) [root@docker shop-api]# yum install -y python-pip [root@docker shop-api]# pip install flask [root@docker shop-api]# python app.py #http://192.168.1.5:5000 [root@docker shop-api]# vim Dockerfile #Base image FROM peter/python-ssh #Maintainer MAINTAINER Peter.Wang # Python env RUN useradd -s /sbin/nologin -M www ADD app.py /opt/app.py ADD requirements.txt /opt/ ADD supervisord.conf /etc/supervisord.conf ADD app-supervisor.ini /etc/supervisord.d/ # Update pip RUN /usr/bin/pip2.7 install -r /opt/requirements.txt # Prot EXPOSE 22 5000 #CMD CMD ["/usr/bin/supervisord","-c","/etc/supervisord.conf"] [root@docker shop-api]# cp /etc/supervisord.conf . [root@docker shop-api]# vim app-supervisor.ini [program:shop-api] command=/usr/bin/python2.7 /opt/app.py process_name=%(program_name)s autostart=true user=www stdout_logfile=/tmp/app.log stderr_logfile=/tmp/app.error [program:sshd] command=/usr/sbin/sshd -D process_name=%(program_name)s autostart=true [root@docker shop-api]# vim supervisord.conf nodaemon=true ; (start in foreground if true;default false) [root@docker shop-api]# docker build -t peter/shop-api . [root@docker shop-api]# docker run --name shop-api -d -p 88:5000 -p 8022:22 peter/shop-api
企业级Docker镜像仓库Harbor
官方网站
官方安装说明
官方使用说明
mkdir -p /server/tools cd /server/tools yum install docker-compose -y # Compose是Docker的管理工具,主要用来构建基于Docker的复杂应用,Compose 通过一个配置文件来管理多个Docker容器,非常适合组合使用多个容器进行开发的场景。 wget https://github.com/vmware/harbor/releases/download/v1.2.0/harbor-offline-installer-v1.2.0.tgz tar xf harbor-online-installer-v1.2.0.tgz cd harbor vim harbor.cfg #The IP address or hostname to access admin UI and registry service. #DO NOT use localhost or 127.0.0.1, because Harbor needs to be accessed by external clients. hostname = docker.test.com # 主机名 #The protocol for accessing the UI and token/notification service, by default it is http. #It can be set to https if ssl is enabled on nginx. ui_url_protocol = http # 协议 #The password for the root user of mysql db, change this before any production use. db_password = 123456 # 数据库root密码 ./install.sh #在Windows做hosts解析 10.0.0.5 docker.test.com [root@lb01 harbor]# docker-compose ps Name Command State Ports ------------------------------------------------------------------------------------------------------------------------------ harbor-adminserver /harbor/harbor_adminserver Up harbor-db docker-entrypoint.sh mysqld Up 3306/tcp harbor-jobservice /harbor/harbor_jobservice Up harbor-log /bin/sh -c crond && rm -f ... Up 127.0.0.1:1514->514/tcp harbor-ui /harbor/harbor_ui Up nginx nginx -g daemon off; Up 0.0.0.0:443->443/tcp, 0.0.0.0:4443->4443/tcp, 0.0.0.0:80->80/tcp registry /entrypoint.sh serve /etc/ ... Up 5000/tcp #http://docker.test.com