Docker入门
1. 简介
(1)容器(Container) vs 虚拟机(Virtual Machine)
容器在Linux上本机运行,并与其他容器共享主机的内核。 它运行一个独立的进程,不占用任何其他可执行文件的内存,使其轻量级。
相比之下,虚拟机(VM)运行一个完整的“客户”操作系统,通过虚拟机管理程序对主机资源进行虚拟访问。 通常,VM提供的环境比大多数应用程序需要的资源更多。
容器优势:
- 更高效利用系统资源
- 更快启动时间
- 一致的运行环境
- 轻松迁移
- 容易维护拓展
- 持续交付和部署
(2) 容器(container) 镜像(image) 仓库(repository) registry
镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数。镜像不包含任何动态数据,其内容再构建之后不会改变。
容器是镜像运行时的实体,可以被创建、启动、停止、删除、暂停等。容器的实质是独立的进程。
仓库是镜像的集合,仓库有多个Tag,每个Tag对应一个镜像。
Registry是仓库的集合,是一种集中存储、分发镜像的服务。公开的registry有docker hub等,也可以搭建私有的registry
2. Docker安装
docker store注册登录,下载dmg安装
https://store.docker.com/editions/community/docker-ce-desktop-mac
(3) 简单配置
启动docker,登录docker id
添加国内镜像地址 http://cbe850dc.m.daocloud.io
(3) 验证安装
docker --version #版本信息
docker version #详细版本信息
docker info #统计信息
docker run hello-world #从默认registry的镜像仓库下载并运行hello-world镜像
docker image ls #显示本地已有的镜像,其中有hello-world镜像
2. 实现hello-docker
该部分利用docker容器技术,实现了一个基于flask的网页
(1) 准备
ifconfig -a#查看本机在虚拟网络vboxnet1中的IP地址,192.168.99.1
#vboxnet1: inet 192.168.99.1 netmask 0xffffff00 broadcast 192.168.99.255
mkdir hello#创建空目录hello
cd hello #进入目录
touch Dockerfile #创建Dockerfile, docker镜像描述文件
touch app.py #创建app.py, flask网页服务端代码
touch requirements.txt #创建python额外依赖包列表文件
(2) Dockerfile
# 使用docker官方提供的python运行环境镜像
FROM python:2.7-slim
# 设置工作目录
WORKDIR /app
# 将当前目录下的内容拷贝到工作目录
ADD . /app
# 安装python镜像额外的python包
RUN pip install --trusted-host pypi.python.org -r requirements.txt
# 把容器的80端口暴露到外部
EXPOSE 80
# 定义环境变量
ENV NAME Docker
# 容器启动运行的命令
CMD ["python", "app.py"]
(3) app.py
# -*- coding: utf-8 -*-
from flask import Flask
from redis import Redis, RedisError
import os
import socket
# 连接Redis
redis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2)
#创建Flask网页
app = Flask(__name__)
#定义访问网页根目录的处理逻辑
@app.route("/")
def hello():
try:
visits = redis.incr("counter")
except RedisError:
visits = "<i>cannot connect to Redis, counter disabled</i>"
html = "<h3>Hello {name}!</h3>" \
"<b>Hostname:</b> {hostname}<br/>" \
"<b>Visits:</b> {visits}"
return html.format(name=os.getenv("NAME", "Docker"), hostname=socket.gethostname(), visits=visits)
if __name__ == "__main__":
#制定Flask网页服务端运行在80端口,接受所有ip对服务端的80端口的访问
app.run(host='0.0.0.0', port=80)
(4)requirements.txt
Flask
Redis
(5)创建hello镜像
docker build -t hello . #创建镜像,-t指定镜像名称
docker image ls #查看新创建的镜像
#REPOSITORY TAG IMAGE ID CREATED SIZE
#hello latest d01960578789 41 seconds ago 132MB
#python 2.7-slim 02ca219cf841 2 weeks ago 120MB
(6)运行hello
docker run -d -p 4000:80 hello #由hello镜像创建容器,-d让容器后台运行,-p指定宿主机器与容器间的端口映射
浏览器访问 http://192.168.99.1:4000
(7)操作docker镜像和容器
docker --help #查看docker帮助
docker image --help #查看image帮助
docker container --help #查看container帮助
docker image ls #查看本地已有image列表
docker image rm 完整镜像名称/镜像id/镜像id前缀 #删除镜像
docker container ls #查看正在运行的容器列表
docker container stop 容器名称/容器id #终止运行的容器
docker container rm 容器名称/容器id #删除运行的容器
(8)分享镜像
docker login #用docker hub的账号密码登录
docker tag hello liangkw16/hello:v1 #给镜像打标签 标签格式:docker hub用户名/仓库标识:版本标识
docker image ls #查看打过标签的image, 一个image可以有多个tag
docker push liangkw16/hello:v1 #向默认registry提交镜像
docker container ls#
#CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
#18eb9e1f1b46 hello "python app.py" 14 minutes ago Exited (0) 10 minutes ago clever_chebyshev
docker container stop 18e#停止容器,18e为容器id前缀
docker container rm 18e#删除容器,18e为容器id前缀
docker image rm liangkw16/hello:v1 #删除本地镜像
docker image rm hello:latest #删除本地镜像
docker run -p 4000:80 liangkw16/hello:v1 #从registry获取镜像并运行
(9)创建私有仓库
为保证镜像上传和下载的速度,搭建一个本地测试用的私有仓库,地址为 192.168.99.1:5000, 客户端配置insecure-registries,并重启docker
docker run -d -p 5000:5000 --restart=always --name registry registry:2 #通过运行官方镜像registry来创建私有仓库,默认仓库的位置是/var/lib/registry, 可以用 -v 上传路径:下载路径 来指定镜像文件上传和下载路径
docker image ls#查看本地镜像
#REPOSITORY TAG IMAGE ID CREATED SIZE
#liangkw16/hello v1 32f1961021f9 18 minutes ago 132MB
#registry 2 b2b03e9146e1 9 days ago 33.3MB
#python 2.7-slim 02ca219cf841 2 weeks ago 120MB
docker tag python:2.7-slim 192.168.99.1:5000/python:2.7-slim #将本地已下载python镜像重新标签
docker push 192.168.99.1:5000/python:2.7-slim #将python镜像上传到本地registry
修改Dockerfile中的From后的python镜像为本地镜像
# 使用docker官方提供的python运行环境镜像
FROM 192.168.99.1:5000/python:2.7-slim
# 设置工作目录
WORKDIR /app
# 将当前目录下的内容拷贝到工作目录
ADD . /app
# 安装python镜像额外的python包
RUN pip install --trusted-host pypi.python.org -r requirements.txt
# 把容器的80端口暴露到外部
EXPOSE 80
# 定义环境变量
ENV NAME Docker
# 容器启动运行的命令
CMD ["python", "app.py"]
重新build镜像,并push到本地私有仓库
docker build -t hello .#重新build镜像
docker tag hello:latest 192.168.99.1:5000/hello:v1 #重新给镜像打标签
docker push 192.168.99.1:5000/hello:v1 #将镜像push到本机私有仓库
docker image rm 192.168.99.1:5000/hello:v1 #删除本地镜像
docker run -p 4000:80 192.168.99.1:5000/hello:v1 #从本地私有仓库拉取镜像并运行
3. 负载均衡实现
在分布式应用程序中,应用程序的不同部分称为“服务”(services)。例如,一个视频共享站点,它包括存储应用程序数据的数据库服务,处理用户上传内容的转码服务,响应用户操作的前端服务等等。
服务只运行一个镜像,但指定了镜像的运行的方式——应该使用哪些端口,应该运行多少个容器副本,以及服务所需的容量等。 可以动态更改运行中服务的容器数量,从而为服务分配更多计算资源。
(1) 创建docker-compose.yml
version: "3" #版本号
services: #服务
web: #服务名称
image: 192.168.99.1:5000/hello:v1 #指定镜像
deploy: #服务部署
replicas: 5 #容器实例数量
resources: #资源情况
limits: #资源限制
cpus: "0.1" #cpu占用不能超过10%
memory: 50M #内存占用不能超过50M
restart_policy: #重启策略
condition: on-failure #失败了就重启
ports: #外部端口和内部端口的映射
- "4000:80"
networks: #指定网络,容器实例共享80端口
- webnet
networks: #负载均衡网络定义
webnet:
(2) 运行负载均衡应用
docker swarm init #初始化swarm集群,只有本机一个节点,保证下一句不出错
docker stack deploy -c docker-compose.yml hello-service #通过docker-compose.yml创建名为hello-servcie的服务,-c指定文yml件
docker service ls #查看服务列表
#ID NAME MODE REPLICAS IMAGE PORTS
#43fhqq2h0aot hello-service_web replicated 5/5 192.168.99.1:5000/hello:v1 *:4000->80/tcp
docker service ps hello-service_web #查看每一个容器实例,加后缀_web
#ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
#q3ovtqbyc9km hello-service_web.1 192.168.99.1:5000/hello:v1 linuxkit-025000000001 Running Running 11 seconds ago
#jjtvp066ugg1 hello-service_web.2 192.168.99.1:5000/hello:v1 linuxkit-025000000001 Running Running 10 seconds ago
#yomq5bxykbth hello-service_web.3 192.168.99.1:5000/hello:v1 linuxkit-025000000001 Running Running 11 seconds ago
#ca59l34iacyq hello-service_web.4 192.168.99.1:5000/hello:v1 linuxkit-025000000001 Running Running 11 seconds ago
#oy0rqrrrsgrf hello-service_web.5 192.168.99.1:5000/hello:v1 linuxkit-025000000001 Running Running 11 seconds ago
docker container ls -q #显示容器id,也就是网页显示的hostname
浏览器访问 http://192.168.99.1:4000, 不断刷新,发现hostname在不断变化,说明service以某种顺序安排不同的容器处理请求,以实现负载均衡
(3) 动态修改service
#修改docker-compose.yml中的replicas: 5为replicas: 6
docker stack deploy -c docker-compose.yml hello-service #重新部署一下,动态对服务的计算资源进行了修改
docker service ls #查看服务列表
#ID NAME MODE REPLICAS IMAGE PORTS
#43fhqq2h0aot hello-service_web replicated 6/6 192.168.99.1:5000/hello:v1 *:4000->80/tcp
(4) 关闭servcie
docker stack rm hello-service #关闭servcie
docker swarm leave --force #关闭swarm集群
4. swarm集群
Swarm是一组运行Docker并加入群集的计算机,这些机器可以是物理机或虚拟机,被称为节点。
- swarm manager节点:执行docker命令,管理swarm集群
- worker节点:提供计算资源
Swarm集群运行容器的策略有多种,比如:“emptiest node”,“global”等。
(1) 创建虚拟机
下载并安装 Virtual Box
https://download.virtualbox.org/virtualbox/5.2.14/VirtualBox-5.2.14-123301-OSX.dmg
docker-machine create --driver virtualbox myvm1#创建虚拟机
docker-machine create --driver virtualbox myvm2#创建虚拟机
docker-machine ls#查看已有虚拟机列表
#NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
#myvm1 - virtualbox Running tcp://192.168.99.100:2376 v18.05.0-ce
#myvm2 - virtualbox Running tcp://192.168.99.101:2376 v18.05.0-ce
(2) 搭建swarm集群
docker-machine ssh myvm1 "docker swarm init --advertise-addr <myvm1 ip>" #myvm1作为swarm manager node,<myvm1 ip>为上一步myvm1的ip
#docker swarm join --token SWMTKN-1-3gux768xxiq33fw6ln1yyfcuf5j6ke5ornvgjxyhzw0f4ptve7-7anykb6sdobckza49q4awwswu 192.168.99.100:2377
docker-machine ssh myvm2 "docker swarm join --token <token> <ip>:2377" #myvm1加入到swarm集群中,<token>为myvm1的token,<ip>为myvm1的ip
docker-machine ssh myvm1 "docker node ls" #查看swarm集群中的节点
#ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
#abs2u42k4mfi92iwq6akdlqfv * myvm1 Ready Active Leader 18.05.0-ce
#ne2mz38eyriey3alyva8dnxer myvm2 Ready Active 18.05.0-ce
(3) 配置虚拟机中docker的仓库地址
docker-machine ssh myvm1 #ssh连接myvm1
#myvm1执行如下指令
sudo touch /etc/docker/daemon.json
sudo chmod 777 /etc/docker/daemon.json
sudo echo '{ "insecure-registries": ["192.168.99.1:5000"] }' > /etc/docker/daemon.json
sudo reboot
docker-machine ssh myvm2 #ssh连接myvm1
#myvm2执行如下指令
sudo touch /etc/docker/daemon.json
sudo chmod 777 /etc/docker/daemon.json
sudo echo '{ "insecure-registries": ["192.168.99.1:5000"] }' > /etc/docker/daemon.json
sudo reboot
(4) docker命令绑定到manager节点
docker-machine env myvm1 #查看mvm1环境变量
eval $(docker-machine env myvm1) #让本机shell的命令发向manager节点
docker-machine ls #查看swarm集群列表,myvm1是当前激活节点
#NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
#myvm1 * virtualbox Running tcp://192.168.99.100:2376 v18.05.0-ce
#myvm2 - virtualbox Running tcp://192.168.99.101:2376 v18.05.0-ce
(5) 在swarm集群上运行应用
docker stack deploy --with-registry-auth -c docker-compose.yml hello-service #myvm1执行docker命令,运行hello-docker-service
docker service ps hello-service_web #查看service运行情况,加_web后缀
#ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
#ot2ulsv5spmk hello-service_web.1 192.168.99.1:5000/hello:v1 myvm2 Running Running 3 seconds ago
#wzpd53kz410a hello-service_web.2 192.168.99.1:5000/hello:v1 myvm1 Running Running 3 seconds ago
#tmdhd3rz4vbn hello-service_web.3 192.168.99.1:5000/hello:v1 myvm1 Running Running 3 seconds ago
#t2z63zyswbsk hello-service_web.4 192.168.99.1:5000/hello:v1 myvm2 Running Running 3 seconds ago
#bmabpujg1x1z hello-service_web.5 192.168.99.1:5000/hello:v1 myvm1 Running Running 3 seconds ago
浏览器访问一下任意地址,并多次刷新,观察hostname变化
http://192.168.99.100:4000
http://192.168.99.101:4000
5.栈(Stack)
栈是一组相互关联的服务,它们共享依赖关系,并且可以协调和组合在一起。 单个堆栈能够定义和协调整个应用程序的功能,非常复杂的应用程序可能会使用多个堆栈。
(1) 向私有库中添加redis镜像
docker pull redis
docker tag redis 192.168.99.1:5000/redis
docker push 192.168.99.1:5000/redis
(2) 一个docker-compose.yml就定义了一个栈,services关键字定义的就是一组相关的服务,这里添加了redis这一服务,来提供存储功能,web网页要实现访问计数,就要依赖redis,同时web和redis使用相同的网络webnet
version: "3" #版本号
services: #服务
web: #服务名称
image: 192.168.99.1:5000/hello:v1 #指定镜像
deploy: #服务部署
replicas: 5 #容器实例数量
resources: #资源情况
limits: #资源限制
cpus: "0.1" #cpu占用不能超过10%
memory: 50M #内存占用不能超过50M
restart_policy: #重启策略
condition: on-failure #失败了就重启
ports: #外部端口和内部端口的映射
- "4000:80"
networks: #指定网络,容器实例共享80端口
- webnet
redis:
image: redis
ports:
- "6379:6379"
volumes: #将主机/home/docker/data挂载到容器的/data目录
- "/home/docker/data:/data"
deploy:
placement: #只在manager节点上存储数据
constraints: [node.role == manager]
command: redis-server --appendonly yes
networks:
- webnet
networks: #负载均衡网络定义
webnet:
(3) manager节点创建data目录
docker-machine ssh myvm1 "mkdir ./data"
(4) 在swarm集群上运行Stack应用
docker stack deploy -c docker-compose.yml hello-service #myvm1执行docker命令,运行hello-service
docker service ls #查看服务列表
#ID NAME MODE REPLICAS IMAGE PORTS
#v1jdk5as6m9y hello-service_redis replicated 1/1 redis:latest *:6379->6379/tcp
#6m9x1e3jp75x hello-service_web replicated 5/5 192.168.99.1:5000/hello:v1 *:4000->80/tcp
浏览器访问一下任意地址,并多次刷新,观察hostname变化,观察访问次数的变化
http://192.168.99.100:4000
http://192.168.99.101:4000
(5) 移除应用,重启swarm集群
docker stack rm hello-service#移除服务
eval $(docker-machine env -u) #恢复本机docker环境
docker-machine ls #查看本机的虚拟机
docker-machine stop myvm1 myvm2 #关闭虚拟机
docker-machine start myvm1 myvm2 #启动虚拟机
6. 总结
(1) Docker引擎
Docker Engine是一个C/S架构的应用程序,包含以下主要组件:
- Docker守护进程——监听并响应docker API请求和docker管理命令
- REST API——提供程序与守护进程进行通信的接口
-
命令行接口客户端——docker命令
(2) Docker架构
Docker使用C/S架构。 Docker客户端与Docker守护进程通信,后者负责构建,运行和分发Docker容器。 Docker客户端和守护程序可以在同一系统上运行,也可以将Docker客户端连接到远程Docker守护程序。 Docker客户端和守护程序使用REST API,通过UNIX套接字或网络接口进行通信。
- Docker守护进程
- Docker客户端
- Docker注册仓库
- Docker对象
- 镜像(image)
- 容器(container)
- 服务(service)
(3) Docker实现技术
Docker是用Go编写的,利用了Linux内核的机制来实现了其功能,主要有以下技术:
命名空间(Namespaces)
Docker使用称为命名空间的技术来提供称为容器的隔离工作空间。 当您运行容器时,Docker会为该容器创建一组名称空间。这些命名空间提供了一层隔离。 容器的每个方面都在一个单独的命名空间中运行,其访问权限仅限于该命名空间。
控制组(Control groups)
cgroup将应用程序限制为特定的一组资源。 控制组允许Docker Engine将可用的硬件资源共享给容器,并可选择强制执行限制和约束。 例如,您可以限制特定容器的可用内存。
UnionFS(Union file systems)
UnionFS是通过创建图层来操作的文件系统,使它们非常轻量级和快速。Docker Engine使用UnionFS为容器提供构建块。 Docker Engine可以使用多种UnionFS变体,包括AUFS,btrfs,vfs和DeviceMapper。
容器格式(container format)
Docker Engine将命名空间,控制组和UnionFS组合到一个称为容器格式的包装器中。 默认的容器格式是libcontainer。 将来,Docker可以通过与BSD Jails或Solaris Zones等技术集成来支持其他容器格式。