1.为什么学docker
某公司的产品运行在内部的虚拟化平台中,如openstack,也就是我们所知道的kvm虚拟机,创建虚拟机.
但是不断增加的云端应用,增加了对硬件资源的消耗,不断的创建虚拟机,给公司带来了难题,公司已经在云平台上运行了多台云主机,消耗了大量的硬件资源.
怎么才能够高效的利用硬件资源实现云服务呢?
容器技术,此时就派上用场了.
openstack
openstack是云管理平台,其本身不提供虚拟化功能,真正的虚拟化功能是由底层的hypervisor(如kvm,qemu,xen等)提供.所谓管理平台,就是为了方便使用而已.
kvm
kvm是最底层的hypervisor,是用来模拟cpu的运行,然而一个用户能在kvm上完成虚拟机的操作还需要network及周边的I/O支持,所以便借鉴了qemu进行一定的修改,形成qemu-kvm.
但是openstack不会直接控制qemu-kvm,会用一个libvirt的库去间接控制qemu-kvm,qemu-kvm的地位就像底层驱动来着.kvm是linux虚拟化的核心管理技术.
docker的诞生
Docker公司位于旧金山,原名dotCloud,底层利用了Linux容器技术(LXC)(在操作系统中实现资源隔离与限制).为了方便创建和管理这些容器,dotCloud开发了一套内部工具,之后被命名为"Docker" Docker就是这样诞生的
Hypervisor:一种运行在基础物理服务器和操作系统之间的中间软件层,可允许多个操作系统和应用共享硬件,常见的VMware的workstation,ESXi,微软的Hyper-V或者思杰的XenServer.
Container Runtime:通过Linux内核虚拟化能力管理多个容器,多个容器共享一套操作系统内核,因此摘掉了内核占用的空间及运行所需要的耗时,使得容器极其轻量与快速
2.容器技术
Docker最初是DotCloud公司在发过期间发起的一个公司内部项目,后来以Apache2.0授权协议开源,代码在GitHub上维护
Docker是基于Google公司退出的Golang语言开发而来,基于Linux内核Cgroups,NameSpace,以及Union FS等技术,对进程进行封装隔离,属于操作系统层面的虚拟化技术
由于隔离的进程独立于宿主机和其他隔离的进程,也被称之为容器.
最初的Docker是基于LXC的,后来去除LXC转而使用自行开发的Libcontainer.
Docker被定义为开源的容器引擎,可以方便的对容器进行管理.例如对镜像打包封装,引入Docker Registry对镜像统一管理
利用Docker可以实现开发,测试,生产环境的部署一致性,极大的减少运维成本.
3.容器和虚拟机的差异
传统虚拟机技术
虚拟机是虚拟出一套硬件,在其上面运行一个完整的操作系统,例如我们使用KVM,指定系统镜像,然后装系统,最终可以使用,在该系统上再运行所需的应用程序.
KVM创建虚拟机时,指定较少的cpu,内存,硬盘等资源,虚拟机性能较低.
容器技术
- 容器内的应用程序直接运行在宿主机的内核上
- 容器内没有自己的内核
- 也没有对硬件进行虚拟,因此容器比起虚拟机更为轻便.
容器技术对比虚拟化
- 容器时直接提供宿主机的性能,而kvm虚拟机是分配宿主机硬件资源,性能较弱
- 同样配置的宿主机,最多可以启动10个虚拟机的话,可以启动100+的容器数量.
- 启动一个kvm虚拟机,得有一个完整的开机流程,花费时间较长,或许得20S,而启动一个容器只需要1S.
- KVM需要硬件CPU的虚拟化支持,而容器不需要.
特性 | 容器 | 虚拟机 |
启动 | 秒级 | 分钟级 |
硬盘使用 | 一般为MB | 一般为GB |
性能 | 接近原生 | 弱 |
系统支持量 | 单机支持上千个容器 | 一般几十个 |
资源隔离 | 安全隔离 | 完全隔离 |
4.为什么选择docker
docker更高效的利用系统资源
容器不需要进行硬件虚拟化以及运行一个完整操作系统的额外开销,docker对系统资源的利用率更高,无论是应用执行,文件存储,还是在内存消耗等方面,都比传统虚拟机更高效.因此一个同样配置的主机,可以运行出更多数量的容器实例.
5.Docker架构
用docker前运维难题
用docker后的运维架构
前提是,你们的开发,运维都好好的学习了docker技术,否则docker带来的,是更复杂的维护成本.
docker引擎架构
Docker Daemon
安装使用Docker,得先运行Docker Daemon进程,用于管理docker,如:
- 镜像 images
- 容器 containers
- 网络 network
- 数据卷 Data Volumes
Rest接口
提供和Daemon交互的API接口
写代码,直接和docker主进程交互,对容器管理.
使用docker rest api管理容器代码示例
前提是我们的docker服务需要使其能允许远程访问
[root@localhost ~]# systemctl status docker #找到启动service
● docker.service - Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled)
Active: active (running) since 三 2023-10-11 15:33:56 CST; 2 days ago
# 修改/usr/lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2375
systemctl daemon-reload
systemctl restart docker.service
import requests
# 设置Docker REST API的URL
# 获取所有容器
response = requests.get('http://10.0.0.76:2375/containers/json')
containers = response.json()
# 输出容器列表
for container in containers:
print(container['Names'][0])
# 创建一个新容器
data = {
'name': 'my-container',
'image': 'ubuntu:latest',
'command': 'echo Hello, World!'
}
response = requests.post('http://10.0.0.76:2375/containers/create', json=data)
new_container = response.json()
print(new_container)
# 启动容器
start_url = f'http://10.0.0.76:2375/containers/{new_container["Id"]}/start'
response = requests.post(start_url)
if response.status_code != 204:
print("容器启动失败")
else:
print("容器启动成功")
# 停止容器
stop_url = f'http://10.0.0.76:2375/containers/{new_container["Id"]}/stop'
response = requests.post(stop_url)
if response.status_code != 204:
print("容器关闭失败")
else:
print("容器关闭成功")
# 删除容器
delete_url = f'http://10.0.0.76:2375/containers/{new_container["Id"]}'
response = requests.delete(delete_url)
if response.status_code != 204:
print("容器删除失败")
else:
print("容器删除成功")
当然还有镜像管理的api方法,在这里我不做过多的演示了,这在我们做自动化运维平台的时候可能会用到
Docker Client
客户端使用REST API和Docker Daemon进行访问
运维常用的docker维护命令.
Docker组件工作流
Images
镜像是一个特殊的文件系统,除了提供容器运行时所需的程序,库,资源,配置文件等外,还包含了一些为运行时准备的配置参数
镜像不包含任何动态数据,其内容在构建之后也不会被改变.
镜像是一个只读模板,用于创建容器,也可以通过Dockerfile文件描述镜像的内容.
镜像的概念类似于变成开发里面面向对象的类,从一个基类开始(基础镜像Base Image)
构建容器的过程,就是运行镜像,生成容器实例
Docker镜像的描述文件是Dockerfile,包含了如下的指令:
- FORM 定义基础镜像
- MAINTAINER 作者
- RUN 运行Linux命令
- ADD 添加文件/目录
6.Docker实操
docker安装部署
1.国内源安装docker-ce
配置linux内核流量转发功能
因为 docker和宿主机的端口映射,本质是内核的流量转发功能
## 若未配置,需要执行如下
cat << EOF > /etc/sysctl.d/docker.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF
# 加载内核防火墙模板,允许流量转发
[root@localhost ~]# modprobe br_netfilter
[root@localhost ~]#
[root@localhost ~]#
[root@localhost ~]# sysctl -p /etc/sysctl.d/docker.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
配置清华,阿里源皆可
# 基础环境配置
yum remove docker docker-common docker-selinux docker-engine -y
yum install yum-utils device-mapper-persistent-data lvm2 -y
# 2
wget -O /etc/yum.repos.d/docker-ce.repo https://download.docker.com/linux/centos/docker-ce.repo
#3
sed -i 's#download.docker.com#mirrors.aliyun.com/docker-ce#g' /etc/yum.repos.d/docker-ce.repo
#4
yum makecache fast
# 安装docker-ce 社区版,免费版 docker
yum install docker-ce -y
# 启动docker
systemctl start docker
# 自启动docker
systemctl enable docker
# 查看docker服务端进程
ps -ef |grep docker
# 为什么要配置加速地址
docker pull xx 默认从国外站点dockerhub上下载镜像,太慢
# 配置加速地址
mkdir -p /etc/docker
tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://s3ffa44s.mirror.aliyuncs.com"]
}
EOF
systemctl daemon-reload
systemctl restart docker
Docker服务端版本
docker version
2.初体验docker玩法
搜索官网镜像
docker search 镜像名:镜像版本 # 语法
docker search nginx # 默认最新版本nginx:latest
下载官网镜像
docker pull nginx
语法
docker pull 镜像名:tag 版本
docker pull nginx:latest
下载过程
[root@localhost yum.repos.d]# docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
a2abf6c4d29d: Pull complete
a9edb18cadd1: Pull complete
589b7251471a: Pull complete
186b1aaa4aa6: Pull complete
b4df32aa5a72: Pull complete
a0bcbecc962e: Pull complete
Digest: sha256:0d17b565c37bcbd895e9d92315a05c1c3c9a29f762b011a10c54a66cd53c9b31
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest
docker的镜像下载是下载了一层一层的镜像文件,组合而来
可以通过命令docker history nginx看到分层关系
查看镜像列表
docker images
运行官网镜像
# 端口映射,你才能访问容器内的东西
# -p 28877:80 将宿主机的28877流量转发给容器内目标端口 80端口
# -d 后台运行docker实例进程
docker run -d -p 28877:80 nginx
获取docker hub可用镜像版本
# 基于http接口,访问docker hub的官网仓库API,获取json数据结果
curl -s https://registry.hub.docker.com/v1/repositories/centos/tags |jq # 目前这条链接已经失效了
可以直接访问Docker Hub官方网站(https://hub.docker.com/)
在搜索框输入你想要获取版本信息的镜像名称,并点击进入,打开该镜像的详细信息页面,点击"Tags"选项卡这将显示所有可用的版本信息
运行镜像,生成容器
1,本地运行模式,nginx只在容器内的网络空间运行,不对外(docker run 别加端口映射参数)
# 获取指定版本镜像
docker pull nginx:1.19.7
# 启指定nginx版本容器
docker run nginx:1.19.7 # 前台运行,日志打印在前台
docker run -d nginx:1.19.7 # 后台运行docker进程
# --name 指定容器名字
docker run -d --name nginx_proxy nginx:1.19.7
docker ps # 查看刚启动创建的容器
# 查看容器ip,宿主机是可以和这个容器通信的(非docker宿主机不行)
# 查看容器网络信息,输出json
[root@localhost ~]# docker inspect nginx_proxy |grep -i "ipaddr"
"SecondaryIPAddresses": null,
"IPAddress": "172.17.0.4",
"IPAddress": "172.17.0.4",
=======================================================================================
# 宿主机访问,通过docker0 访问本地容器的nginx
[root@localhost ~]# curl -I 172.17.0.4
HTTP/1.1 200 OK
Server: nginx/1.19.7
Date: Thu, 12 Oct 2023 03:00:10 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 16 Feb 2021 15:57:18 GMT
Connection: keep-alive
ETag: "602beb5e-264"
Accept-Ranges: bytes
2,对外运行模式
docker run -d -p 80:80 nginx:1.19.7
查看容器运行列表
docker
停止容器,记录
docker stop nginx_proxy
#重启
[root@localhost ~]# docker start nginx_proxy
nginx_proxy
删除容器,记录
docker stop nginx_proxy
docker rm nginx_proxy
批量停止运行中的容器(危险命令,慎用)
查询容器,且只显示容器id号
# 查询所有运行中的容器id号
[root@localhost ~]# docker ps -q
c378edc721e9
ab14ede9aed1
79b58c097de1
# 查询所有,以及挂掉的容器,id号
docker ps -aq
# 批量停止正在运行中的容器
docker stop $(docker ps -q)
# 批量删除已经停止的容器
docker rm `docker ps -`
进入容器
# docker run的新参数
# -t 开启一个终端
-t, --tty Allocate a pseudo-TTY
# 标准输入,给容器输入些东西
-i, --interactive Keep STDIN open even if not attached
docker run -it ubuntu:latest /bin/bash
自定制docker镜像
需求:
1, docker hub下载一个centos7.4.1708 最小化的系统,基础镜像
2, 在此基础环境上,部署新的,你需要的环境,提供网络工具包,net-tools,vim,nginx自定制镜像
3, 基于这个镜像,快速生成n个, nginx环境,提供vim, netstat,ifconfig...命令
4, 这个自定制镜像推送到镜像仓库,提供下载,离线导出,发送给局域网的其他机器使用.
# 基础环境
docker run -it centos:7.4.1708 bash
# 清空原有yum环境
rm -f /etc/yum.repos.d/*
# 下载新的yum源
curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo
curl -o /etc/yum.repos.d/epel.repo https://mirrors.aliyun.com/repo/epel-7.repo
# 下载应用
yum install vim net-tools nginx -y
# 最后清空缓存,降低容器内资源的占用
yum clean all
==================================================================================================
推送自定制本地镜像到docker仓库中,提供下载
https://hub.docker.com
注册账密,即可登陆
本地登陆docker hub
docker login
推送本地镜像到docker hub
得将镜像名,改为docker hub账号开头的规则
docker tag 镜像名 freepengyang/vim-net-tools-nginx-centos7
docker tag centos:7.4.1708 freepengyang/vim-net-tools-nginx-centos7
docker push freepengyang/vim-net-tools-nginx-centos7
# 从hub下载刚才推送的自定制镜像
# 先删除本地自定制镜像
docker rmi freepengyang/vim-net-tools-nginx-centos7
docker pull freepengyang/vim-net-tools-nginx-centos7:latest
# 容器前台运行nginx第一种方式
# 重新生成一个容器docker run freepengyang/vim-net-tools-nginx-centos7
# 进入容器空间 docker exec -it ffd2afc866e8 bash
# 前台的方式启动容器内的nginx进程
[root@ffd2afc866e8 /]# nginx -g "daemon off;"
# 容器前台运行nginx第二种方式
docker run -d -p 80:80 freepengyang/vim-net-tools-nginx-centos7:v2 nginx -g "daemon off;"
# 提交容器记录,生成新的镜像记录
# 先删除之前的镜像docker rmi freepengyang/vim-net-tools-nginx-centos7
[root@localhost ~]# docker commit --help
Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
Create a new image from a container's changes
Aliases:
docker container commit, docker commit
Options:
-a, --author string Author (e.g., "John Hannibal Smith <[email protected]>")
-c, --change list Apply Dockerfile instruction to the created image
-m, --message string Commit message
-p, --pause Pause container during commit (default true)
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ffd2afc866e8 centos:7.4.1708 "/bin/bash" 21 minutes ago Up 21 minutes modest_yonath
[root@localhost ~]# docker commit ffd2afc866e8 freepengyang/vim-net-tools-nginx-centos7:v2
sha256:d3de274d24b2518e0196ce8ed83a3ef6022110a66e9b2920fa16de3281bf87a2
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
freepengyang/vim-net-tools-nginx-centos7 v2 d3de274d24b2 4 seconds ago 307MB
======================================================================================================
#想把镜像发给其他机器运行
1,将本地镜像提交到镜像仓库
2,本地导出
[root@localhost ~]# docker save freepengyang/vim-net-tools-nginx-centos7:v2 > /opt/vim-net-tools-nginx-centos7.tar.gz
[root@localhost ~]# du -h /opt/vim-net-tools-nginx-centos7.tar.gz
304M /opt/vim-net-tools-nginx-centos7.tar.gz
3,发给目标机器
docker load 导入tar.gz文件
[root@localhost ~]# docker load -i /opt/vim-net-tools-nginx-centos7.tar.gz
9d785f795f3b: Loading layer [==================================================>] 114MB/114MB
Loaded image: freepengyang/vim-net-tools-nginx-centos7:v2
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
freepengyang/vim-net-tools-nginx-centos7 v2 d3de274d24b2 3 hours ago 307MB
4,基于刚导入的镜像运行容器启动nginx
[root@localhost ~]# docker run -d -p 17799:80 freepengyang/vim-net-tools-nginx-centos7:v2 nginx -g "daemon off;"
a9cfe8f00a3298f6e8f0250259c62b53695e51b2b68aec54b783206ab1980e5e
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a9cfe8f00a32 freepengyang/vim-net-tools-nginx-centos7:v2 "nginx -g 'daemon of…" 2 seconds ago Up 2 seconds 0.0.0.0:17799->80/tcp, :::17799->80/tcp laughing_buck
=======================================================================================================
# 清除之前遗留的测试容器和镜像
docker stop `docker ps -aq`
docker rm `docker ps -aq`
docker rmi freepengyang/vim-net-tools-nginx-centos7:v2
回顾镜像管理常用命令
围绕增删改查
# 新增镜像的命令
docker pull 镜像名:tag
docker commitid 容器id # 将容器制作成镜像
docker load < 本地镜像tar文件
docker save 镜像id > 具体镜像tar文件
docker build Dockerfile # 基于dockerfile生成一个新的镜像
#删
docker rmi 镜像id
# 改
#镜像改不了,只读文件模板(如何理解修改镜像)
#1.运行镜像,生成读写层容器实例.
docker run 镜像id # 基于镜像,运行容器实例,该容器的文件系统,都是容器里的资料,不会修改镜像本身
docker centos:7.9.2009 yum install vim -y # 开辟一个读写层,容器层,在这容器里的文件系统中去安装nginx软件
#2.提交该容器空间的,携带vim的记录为新的镜像
docker commitid e40 linux0224-vim-centos7
3.此时新镜像就可以使用centos+vim了
docker tag 修改镜像的版本信息,以及镜像名
# 查
# 查询只读镜像文件的一层层关系
docker history 镜像id
# 列出本地所有镜像
docker images
回顾容器管理常用命令
围绕增删改查
# 增
docker run 镜像id # 基于镜像,生成一个新的容器进程
# docker run常用选项
docker run
-i
-t
-d
-p
-P
-v
docker start 容器id # 尝试启动一个容器
# 删
docker rm 容器id #删除一个容器记录
docker rm -f 容器id #先stop容器,然后再run删除记录
改,修改容器运行记录,状态
docker commitid 容器id # 提交容器记录为新的镜像
docker rename 旧容器名 新容器名
docker stop 容器id # 停止一个运行中的容器
docker restart 容器id # 重启一个容器进程
docker export 容器id # 直接将docker容器进程导出为一个镜像tar包
docker import tar包 # 导入为新镜像
# 查
docker ps #查询运行中的容器
docker ps -a # 查询容器的历史记录,运行的挂掉的
docker ps -aq #查询所有的容器记录,只显示容器id号
docker top 容器id # 查看一个运行中的容器进程信息
docker port 容器id # 查看容器的端口映射情况
docker inspect 容器id # 输出容器的详细json数据信息
docker logs 容器id # 输出容器内的stout,sterr的日志
docker exec 容器id bash -c "执行的sh命令" # 在容器里非交互式执行命令
docker stats 容器id # 查看运行中的容器,使用资源情况 cpu,内存,磁盘等linux性能指标
7.Dockerfile构建镜像
定制docker镜像的方式有两种
手动修改容器内容,导出新的镜像
基于Dockerfile自行编写指令,基于指令流程创建镜像.
dockerfile简介
镜像是多层存储,每一层在前一层的基础上进行修改
容器也是多层存储,以镜像为基础层,在其基础上加一层作为容器运行时的存储层
官网提供的dockerfile实例
https://github.com/CentOS/CentOS-Dockerfiles
dockerfile指令说明
细节要求
1.docker会逐行获取dockerfile中每一行的指令,按顺序解析,实现images的自动构建
2.通过docker build命令构建镜像
3.dockerfile只认识自己规定的指令,不支持自定义
4.大小写不明感,但是建议指令用纯大写
dockerfile主要组成部分:
基础镜像信息FROM centos:6.8
制作镜像操作指令RUN yum install openssh-server -y
容器启动时执行指令CMD ["/bin/bash"]
dockerfile指令详解
FROM
FROM base镜像
必须在docker第一行,表示从哪个基础镜像构建
MAINTAINER
MAINTAINER
可选,填入作者信息,如MAINTAINER free彭
RUN
RUN
每一个RUN指令都是单独开启一个镜像层(镜像优化里需要关注)
可以写入多个RUN,自上而下执行RUN命令
语法一
RUN <cmd> 会被当作/bin/bash -c "cmd"执行
语法二
RUN ["命令", "参数1", "参数2"]docker会将其解析为json,因此必须是双引号,且命令必须是绝对路径
RUN原理
RUN后面的命令,每一个RUN本质上就是开启一个容器,执行命令,然后提交执行结果加入为新的一层镜像记录.
前一个RUN的执行结果,仅仅是针对当前进程,在内存中产生的数据变化.
后一个RUN的执行,会产生新的容器,和第一个RUN容器没有任何关系,也不会继承前一层构建产生的内存变化.
如果你要并联执行2个命令,可以用如cd /opt && echo "嘻嘻嘻"
CMD
CMD
- CMD是专门用于容器运行时候的默认命令
- 当容器运行时,你也可以在命令结尾,主动添加command,就会覆盖dockerfile构建的镜像中的默认cmd;
- 当dockerfile里只能写一个CMD,即使写多个,只有最后一个CMD生效;
- CMD定义语法
CMD <cmd>以/bin/bash -c "cmd"执行
CMD <"executable", "args1", "args2">
CMD ["cmd", "arg1", "arg2"] 这里要注意, ENTRYPOINT的参数了
参考用法
CMD ["nginx", "-g", "daemon off;"]
EXPOSE
EXPOSE
声明容器暴露端口;
语法: EXPOSE <port1> <port2>
EXPOSE只是声明容器运行时,提供哪个对外的服务端口,并不会自己开启,需要主动用参数映射;
-p <宿主机端口>:<容器端口>
- 作用1是帮助运维理解这个镜像所开启的端口,便于配置映射关系
- 作用2是,使用随机映射的话,也会使用EXPOSE打开的端口
ENTRYPOINT
ENTRYPOINT
- 翻译为入口点,可以理解为,将最终的容器变成一个可执行的文件
- ENTRYPOINT 的格式和RUN指令格式一样,分为exec格式和shell格式.
- ENTRYPOINT 的目的和CMD一样,都是在指定容器启动程序及参数.
- 当指定了ENTRYPOINT后,CMD的含义就会发生了改变,不再是直接的运行其命令,而是将CMD的内容作为参数传给ENTRYPOINT
- 换句话说实际执行时,将变为:
<ENTRYPOINT> "<CMD>"
让镜像变成命令一样使用
假设我们需要一个得知自己当前公网IP的镜像,那么可以先用CMD来实现
FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
CMD ["curl", "http://myip.ipip.net"]
假设我们使用docker build -t myip 来构建镜像的话,如果我们需要查询当前公网IP,只需要执行:
docker run myip
ADD和COPY
1.如源码编译这个需求,可以用ADD,COPY将宿主机上的文件,复制到镜像里
2.ADD和COPY的源路径,必须是以dockerfile所在目录的相对路径
3.原路径也可以是一个网络URL
4.ADD比COPY多一个解压tar的功能.
ADD详解
ADD指令和COPY的格式和性质基本一致,但是在COPY基础上增加了一些功能.
比如<源路径>可以是一个URL,这种情况下,Docker引擎会试图去下载这个链接的文件放到<目标路径>上去.
下载后的文件权限自动设置为600,如果这并不是想要的权限,那么还需要增加额外的一层RUN进行权限调整,另外如果源路径是一个网络url的话下载这个链接的文件后还需要另外执行指令去修改权限,
所以不如直接使用RUN命令,然后使用wget或者curl工具下载,处理权限,解压缩,解压缩,然后清理无用文件更合理.
因此这个功能其实并不实用,而且不推荐使用
如果<源路径>为一个tar压缩文件的话,压缩格式为gzip,bzip2以及xz的情况下,ADD指令将会自动解压缩这个压缩文件
ENV
格式有两种:
ENV <key> <value>
ENV <key1>=<value> <key2>=<value>...
这个指令很简单,就是设置容器运行时的的环境变量而已
无论是后面的其它指令,如RUN,还是运行时的应用,都可以直接使用这里定义的环境变量.
WORKDIR
格式为 WORKDIR <工作目录路径>.
使用WORKDIR指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR会帮你建立目录
之前提到一些初学者常犯的错误,Dockerfile等同于shell脚本来书写,这种错误的理解还可能会导致出现下面这样的错误:
RUN cd /app
RUN echo "hello" > world.txt
如果将这个dockerfile进行构建镜像运行后,会发现找不到/app/world.txt文件,或者其内容不是hello
原因很简单,在shell中,连续两行是同一个进程执行环境,因此前一个命令修改的内存状态会直接影响后一个命令;而在dockerfile中
这两行RUN命令的运行环境根本不同,是两个完全不同的容器,这就是读dockerfile构建分层存储概念不了解所导致的错误.
USER
格式:USER <用户名>[:<用户组>]
USER指令和WORKDIR相似,都是改变环境状态并影响以后的层,WORKDIR是改变工作目录,USER则是改变之后层的执行,RUN,CMD以及ENTRYPOINT这类命令的身份.
注意,USER只是帮助你切换到指定用户而已,这个用户必须事先建立好的,否则无法切换.
RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN ["redis-server"]
如果以root执行的脚本,在执行期间希望改变身份,比如希望以某个已经建立好的用户来运行某个服务进程,不要使用su或者sudo,这些都需要比较麻烦的配置,而且在TTY缺失的环境下经常出错.
# 建立redis用户,并使用gosu换另外一个用户执行命令
RUN groupadd -r redis && useradd -r -g redis redis
# 下载gosu
RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.12/gosu-amd64" \
&& chmod +x /usr/local/bin/gosu \
&& gosu nobody true
# 设置CMD,并以另外的用户执行
CMD ["exec", "gosu", "redis", "redis-server"]
VOLUME
用来创建一个在镜像之外的挂载点,用于在多个容器之间共享数据
格式为:
VOLUME ["<路径1>", "<路径2>"...]
VOLUME <路径>
之前我们说过,容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中
为了防止运行时用户忘记将动态文件所保存目录挂载为卷(忘记用-v参数),在dockerfile中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据.
VOLUME /data
这里的/data目录就会在容器运行时自动挂载为匿名卷,任何向/data中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化.
当然,运行容器时可以覆盖这个挂载设置,比如:
docker run -d -v mydata:/data xxx
在这行命令中,就使用了mydata这个命名卷挂载到了/data这个位置,替代了dockerfile中定义的匿名卷的挂载配置.
dockerfile多阶段构建
之前的做法
在Docker 17.05版本之前,我们构建Docker镜像时,通常会采用两种方式:
一种方式是将所有的构建过程包含在一个Dockerfile中,包括项目及其依赖库的编译,测试,打包等流程,这里可能会带来一些问题:
- Dockerfile特别长,可维护性降低
- 镜像层次多,镜像体积较大,部署时间变长
- 源代码存在泄漏风险
例如,编写app.go文件,该程序输出Hello World!
package main
import "fmt"
func main(){
fmt.Printf("Hello World!");
}
编写Dockerfile文件
FROM golang:1.9-alpine
RUN apk --no-cache add git ca-certificates
WORKDIR /go/src/github.com/go/helloworld/
COPY app.go .
RUN go get -d -v github.com/go-sql-driver/mysql \
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . \
&& cp /go/src/github.com/go/helloworld/app /root
WORKDIR /root/
CMD ["./app"]
构建镜像
$ docker build -t go/helloworld:1 -f Dockerfile .
另外一种方式,就是我们事先在一个Dockerfile将项目及其依赖库编译测试打包好后,再将其拷贝到运行环境中,这种方式需要我们编写两个Dockerfile和一些编译脚本才能将其两个阶段自动整合起来,这种方式虽然可以很好的规避第一种方式存在的风险,但明显部署过程较复杂.
例如
编写Dockerfile.build文件
FROM golang:1.9-alpine
RUN apk --no-cache add git
WORKDIR /go/src/github.com/go/helloworld
COPY app.go .
RUN go get -d -v github.com/go-sql-driver/mysql \
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
编写Dockerfile.copy文件
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY app .
CMD ["./app"]
新建build.sh
#!/bin/sh
echo Building go/helloworld:build
docker build -t go/helloworld:build . -f Dockerfile.build
docker create --name extract go/helloworld:build
docker cp extract:/go/src/github.com/go/helloworld/app ./app
docker rm -f extract
echo Building go/helloworld:2
docker build --no-cache -t go/helloworld:2 . -f Dockerfile.copy
rm ./app
现在运行脚本即可构建镜像
$ chmod +x build.sh
$ ./build.sh
对比两种方式生成的镜像大小
使用多阶段构建
为解决以上问题,Dockerfile V17.05开始支持多阶段构建,使用多阶段构建我们就可以很容易解决前面提到的问题,并且只需要编写一个Dockerfile:
例如:编写Dockerfile
FROM golang:1.9-alpine as builder
RUN apk --no-cache add git
WORKDIR /go/src/github.com/go/helloworld/
RUN go get -d -v github.com/go-sql-driver/mysql
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest as prod
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/go/helloworld/app .
CMD ["./app"]
构建镜像
$ docker build -t go/helloworld:3 .
对比三个镜像大小
很明显使用多阶段构建的镜像体积小,同时也完美解决了上边提到的问题.
只构建某一阶段的镜像
我们可以使用as来为某一阶段命令,例如
FROM golang:1.9-alpine as builder
例如当我们只想构建builder阶段的镜像时,我们可以在使用docker build命令时加上--target 参数即可
$ docker build --target builder -t username/imagename:tag .
8.Docker访问仓库
仓库(Repository)是集中存放镜像的地方.
一个容易混淆的概念是注册服务器(Registry).实际上注册服务器是管理仓库的具体服务器,每个服务器上可以有多个仓库,而每个仓库下面有多个镜像,从这方面来说,仓库可以被认为是一个具体的项目或目录,例如对于仓库地址dl.dockerpool.com/ubuntu来说,dl.dockerpool.com是注册服务器地址,ubuntu是仓库名. 大部分时候,并不需要严格区分这两者的概念.
公共仓库Docker Hub在前面实操部分已经说过了,我们着重来说下私有仓库
私有仓库
有时候使用Docker Hub这样的公共仓库可能不方便,用户可以创建一个本地仓库供私人使用.
docker-registry 是官方提供的工具,可以用于构建私有的镜像仓库
安装运行docker-registry
你可以通过获取官方registry镜像来运行.
$ docker run -d -p 5000:5000 --restart=always --name registry registry
这将使用官方的registry镜像来启动私有仓库,默认情况下,仓库会被创建在容器的/var/lib/registry目录下,你可以通过-v参数来将镜像文件存放到本地的指定路径,例如下面的例子将上传的镜像存放到本地的/opt/data/registry目录。
docker run -d -p 5000:5000 -v /opt/data/registry:/var/lib/registry --name registry registry
在私有仓库上传,搜索,下载镜像
创建好私有仓库之后,就可以使用docker tag来标记一个镜像,然后推送它到仓库,例如私有仓库地址为127.0.0.1:5000
先在本机查看已有的镜像.
使用docker tag将ubuntu:latest这个镜像标记为127.0.0.1:5000/ubuntu:latest
格式为 docker tag IMAGE[:TAG] [REGISTRY_HOST[:REGISTRY_PORT]/]REPOSITORY[:TAG] 。
使用docker push上传标记的镜像
用curl命令查看仓库中的镜像
这里可以看到{"repositories":["ubuntu"]} ,表明镜像已经被成功上传了。
先删除已有镜像,再尝试从私有仓库中下载这个镜像。
注意事项
如果你不想使用127.0.0.1:5000作为仓库地址,比如想让本网段的其他主机也能把镜像推送到私有仓库,你就得把例如10.0.0.76:5000这样的内网地址作为私有仓库地址,这时你会发现无法成功推送镜像.
这时因为docker默认不允许非https方式推送镜像,我们可以通过docker的配置选项来取消这个限制,或者查看下一节配置能够通过https访问的私有仓库.
这里我实验用的是centos7的系统,在/etc/docker/daemon.json 中写入如下内容(如果文件不存在就新建该文件
{
"registry-mirror": [
"https://registry.docker-cn.com"
],
"insecure-registries": [
"10.0.0.76:5000"
]
}
10.0.0.76为我实验机的内网ip
注意:该文件deamon.json必须符合json规范,否则docker将不能启动.
非https方式推送镜像错误返回如图:
私有仓库高级配置
上一小节我们搭建了一个具有基础功能的私有仓库,本小节我们使用Docker Compose搭建一个拥有认证,tls的私有仓库.
新建一个文件夹,以下步骤均在/etc/docker/registry该文件夹中进行.
最终实验目录结构
准备站点证书
如果你拥有一个域名,国内各大云服务商均提供免费的站点证书,你也可以使用openssl自行签发证书.
这里假设我们将要搭建的私有仓库地址为docker.freepeng.com,下面我们介绍使用openssl自行签发docker.freepeng.com的站点ssl证书.
第一步创建CA私钥
$ openssl genrsa -out "root-ca.key" 4096
第二步利用私钥创建CA根证书请求文件
$ openssl req \
-new -key "root-ca.key" \
-out "root-ca.csr" -sha256 \
-subj '/C=CN/ST=Hangzhou/L=Xiaoshan/O=10heroes/CN=10heroes Doc
ker Registry CA'
以上命令中-subj参数里的/C表示国家,如CN;/ST表示省;/L表示城市或者地区;/O表示组织名;/CN通用名称.
第三步配置CA根证书,新建root-ca.cnf
[root_ca]
basicConstraints = critical,CA:TRUE,pathlen:1
keyUsage = critical, nonRepudiation, cRLSign, keyCertSign
subjectKeyIdentifier=hash
第四步签发根证书.
$ openssl x509 -req -days 3650 -in "root-ca.csr" \
-signkey "root-ca.key" -sha256 -out "root-ca.crt" \
-extfile "root-ca.cnf" -extensions \
root_ca
第五步生成站点ssl私钥.
$ openssl genrsa -out "docker.freepeng.com.key" 4096
第六步使用私钥生成证书请求文件.
$ openssl req -new -key "docker.freepeng.com.key" -out "site.csr" -sha256 \
-subj '/C=CN/ST=Hangzhou/L=Xiaoshan/O=10heroes/CN=docker.freepeng.com'
第七步配置证书,新建site.cnf文件.
[server]
authorityKeyIdentifier=keyid,issuer
basicConstraints = critical,CA:FALSE
extendedKeyUsage=serverAuth
keyUsage = critical, digitalSignature, keyEncipherment
subjectAltName = DNS:docker.freepeng.com, IP:127.0.0.1
subjectKeyIdentifier=hash
第八步签署站点ssl证书.
$ openssl x509 -req -days 750 -in "site.csr" -sha256 \
-CA "root-ca.crt" -CAkey "root-ca.key" -CAcreateserial \
-out "docker.freepeng.com.crt" -extfile "site.cnf" -extensions server
这样已经拥有docker.freepeng.com的网站ssl私钥,docker.freepeng.com.key和ssl证书docker.freepeng.com.crt及根证书root-ca.crt
新建ssl文件夹并将docker.freepeng.com.key docker.freepeng.com.crt root-cat.crt这三个文件移入,删除其他文件.
由于自行签发的CA根证书不被系统信任,所以我们需要将CA根证书root-ca.crt移入/etc/docker/certs.d/docker.freepeng.com 文件夹中
$ sudo mkdir -p /etc/docker/certs.d/docker.freepeng.com
$ sudo cp ssl/root-ca.crt /etc/docker/certs.d/docker.freepeng.com/ca.crt
配置私有仓库
私有仓库默认的配置文件位于/etc/docker/registry/config.yml 我们先在本地编辑config.yml,之后挂载到容器中
version: 0.1
log:
accesslog:
disabled: true
level: debug
formatter: text
fields:
service: registry
environment: staging
storage:
delete:
enabled: true
cache:
blobdescriptor: inmemory
filesystem:
rootdirectory: /var/lib/registry
auth:
htpasswd:
realm: basic-realm
path: /etc/docker/registry/auth/nginx.htpasswd
http:
addr: :443
host: https://docker.freepeng.com
headers:
X-Content-Type-Options: [nosniff]
http2:
disabled: false
tls:
ca_certificate: /etc/docker/certs.d/docker.freepeng.com/ca.crt
certificate: /etc/docker/registry/ssl/docker.freepeng.com.crt
key: /etc/docker/registry/ssl/docker.freepeng.com.key
health:
storagedriver:
enabled: true
interval: 10s
threshold: 3
生成http认证文件
$ mkdir auth
$ docker run --rm \
> --entrypoint sh \
> -v $(pwd)/auth:/auth \
> registry \
> -c "apk add --no-cache apache2-utils && htpasswd -Bbn freepeng 123456 > /auth/nginx.htpasswd"
编辑docker-compose.yml
version: '3'
services:
registry:
image: registry
ports:
- "443:443"
volumes:
- ./:/etc/docker/registry
- registry-data:/var/lib/registry
volumes:
11
registry-data:
yml格式严格,需要注意缩进
修改hosts
编辑/etc/hosts
127.0.0.1 docker.freepeng.com
启动
$ docker-compose up -d
如果docker-compose这个命令的还需要先安装下,命令sudo yum install docker-compose -y
完成
如果上述测试443,证书使用有问题的话,可以尝试使用80测试,需要修改几个地方,config.yml http部分将addr修改成80
host修改成http://docker.freepeng.com
docker-compose.yml 文件将ports部分修改成"80:80"
/etc/docker/daemon.json 文件添加配置项"insecure-registries": ["docker.freepeng.com"]
这个选项insecure-registries设置后,Docker客户端将允许通过http协议而不是https访问这些非安全的仓库,最后重启docker服务,重新启动docker-compose
测试私有仓库功能
注意事项
如果你本机占用了443端口,你可以配置nginx代理,这里不再赘述
Nexus3.x的私有仓库
使用Docker官方的Registry创建的仓库面临一些维护问题,比如某些镜像删除以后空间默认是不会回收的,需要一些命令去回收空间然后重启Registry程序.在企业中把内部的一些工具包放入Nexus中是比较常见的做法,最新版本nexus3.x全面支持Docker的私有镜像,所以使用Nexus3.x一个软件来管理Docker,Maven,Yum,PyPI等是一个明智的选择
启动Nexus容器
$ docker run -d --name nexus3 --restart=always \
-p 8081:8081 \
--mount src=nexus-data,target=/nexus-data \
sonatype/nexus3
等待3-5分钟,如果nexus3容器没有异常退出,那么你可以使用浏览器打开http://YourIP:8081访问Nexus了
第一次启动Nexus的默认账号是admin,密码是admin123登录以后点击页面上方的齿轮按钮进行设置,如果密码不对的话,进入nexus容器docker exec -it a20987ce68f8 bash,执行命令cat /nexus-data/admin.password查看密码
创建仓库
创建一个私有仓库的方法:Repository->Repositories 点击右边菜单Create repository 选择docker(hostsed)
- Name: 仓库的名称
- HTTP: 仓库单独的访问端口
- Enable Docker V1 API:如果需要同时支持V1版本请勾选此项(不建议勾选)
- Hostsed->Deployment pollcy:请选择Allow redeploy否则无法上传Docker镜像.
其它的仓库创建方法请各位自己摸索,还可以创建一个docker(proxy)类型的仓库链接到DockerHub上,再创建一个docker(group)类型的仓库把刚才的hosted与proxy添加在一起,主机在访问的时候默认下载私有仓库中的镜像,如果没有将链接到DockerHub中下载并缓存到Nexus中
添加访问权限
菜单Security->Realms 把Docker Bearer Token Realm移到右边的框中保存
添加用户规则: 菜单 Security->Roles-> Create role 在Privllages 选项搜索docker把响应的规则移动到右边的框中然后保存.
添加用户:菜单 Security->Users-> Create local user 在Roles选项中选中刚才创建的规则移动到右边的窗口保存.
nginx加密代理
证书的生成请看上面私有仓库高级配置里面证书生成一节.
这里我就不用证书了
nginx示例配置如下:
upstream register
{
server "YourHostName OR IP":5001; #端口为上面添加的私有镜像仓库是设置的 HTTP 选项的端口号
check interval=3000 rise=2 fall=10 timeout=1000 type=http;
check_http_send "HEAD / HTTP/1.0\r\n\r\n";
check_http_expect_alive http_4xx;
}
server {
server_name YourDomainName;#如果没有 DNS 服务器做解析,请删除此选项使用本机 IP 地址访问
listen 443 ssl;
ssl_certificate key/example.crt;
ssl_certificate_key key/example.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
large_client_header_buffers 4 32k;
client_max_body_size 300m;
client_body_buffer_size 512k;
proxy_connect_timeout 600;
proxy_read_timeout 600;
proxy_send_timeout 600;
proxy_buffer_size 128k;
proxy_buffers 4 64k;
proxy_busy_buffers_size 128k;
proxy_temp_file_write_size 512k;
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://register;
proxy_read_timeout 900s;
}
error_page 500 502 503 504 /50x.html;
}
测试
当然了还有私有仓库Habor,这个大家可以去大家看看,我就不再演示了,现在大部分中小公司生产环境部署用的镜像仓库都是直接用的云商提供的镜像仓库服务,比如阿里云的镜像仓库啊用的比较多,我们自己搭建的可以用在内网里测试使用也是不错的
9.Docker数据管理
在容器中管理数据主要有两种方式:
数据卷
挂载主机目录
数据卷
数据卷是一个可供一个或多个容器使用的特殊目录,它绕过UFS,可以提供很多有用的特性:
- 数据卷 可以在容器之间共享和重用
- 对数据卷的修改会立马生效
- 对数据卷的更新,不会影响镜像
- 数据卷默认会一直存在,即使容器被删除
注意:数据卷的使用,类似于Linux下对目录或文件进行mount,镜像中的被指定为挂载点的目录中的文件会隐藏掉,能显示看的是挂载的数据卷.
创建一个数据卷
docker volume create my-vol
查看所有的数据卷
$ docker volume ls
local my-vol
在主机里使用以下命令可以查看指定数据卷的信息
$ docker volume inspect my-vol
[
{
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
"Name": "my-vol",
"Options": {},
"Scope": "local"
}
]
启动一个挂载的数据卷的容器
在用docker run命令的时候,使用--mount标记来讲数据卷挂载到容器里,在一次docker run中可以挂载多个数据卷.
下面创建一个名叫web的容器,并加载一个数据卷到容器的/webapp目录.
$ docker run -d -P \
--name web \
# -v my-vol:/wepapp \
--mount source=my-vol,target=/webapp \
training/webapp \
python app.py
查看数据卷的具体信息
在主机里使用以下命令可以查看web容器的信息
$ docker inspect web
数据卷信息在"Mounts" Key下面
"Mounts": [
{
"Type": "volume",
"Name": "my-vol",
"Source": "/var/lib/docker/volumes/my-vol/_data",
"Destination": "/app",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
]
删除数据卷
$ docker volume rm my-vol
数据卷是被设计用来持久化数据的,它的生命周期独立于容器,Docke不会再容器被删除后自动删除数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的数据卷,如果需要在删除容器的同时删除数据卷,可以在删除容器的时候使用docker rm -v这个命令,无主的数据卷可能会占据很多空间,要清理请使用以下命令
$ docker volume prune
挂载主机目录
挂载一个主机目录作为数据卷
使用--mount 标记可以指定挂载一个本地主机的目录到容器中去.
$ docker run -d -P \
--name web \
# -v /src/webapp:/opt/webapp \
--mount type=bind,source=/src/webapp,target=/opt/webapp \
training/webapp \
python app.py
上面的命令记载主机的/src/webapp目录到容器的/opt/webapp目录,这个功能在进行测试的时候十分方便,比如用户可以放置一些程序到本地目录中,来查看容器是否正常工作,本地目录的路径必须是绝对路径,以前使用-v参数时如果本地目录不存在Docker会自动为你创建一个文件夹,现在使用--mount参数时如果本地目录不存在,Docker会报错.
Docker挂载主机的目录权限默认是读写,用户也可以通过增加readonly指定为只读.
$ docker run -d -P \
--name web \
# -v /src/webapp:/opt/webapp:ro \
--mount type=bind,source=/src/webapp,target=/opt/webapp,readonly \
training/webapp \
python app.py
加了readonly之后,就挂载为只读了,如果你在容器内/opt/webapp目录新建目录,会显示如下错误
/opt/webapp # touch new.txt
touch: new.txt: Read-only file system
查看数据卷的具体信息
在主机里使用以下命令可以查看web容器的信息
$ docker inspect web
挂载主机目录的配置信息在Mount Key下面
"Mounts": [
{
"Type": "bind",
"Source": "/src/webapp",
"Destination": "/opt/webapp",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
挂载一个本地主机文件作为数据卷
--mount标记也可以从主机挂载单个文件到容器中
$ docker run --rm -it \
# -v $HOME/.bash_history:/root/.bash_history \
--mount type=bind,source=$HOME/.bash_history,target=/root/.bash_history \
ubuntu:17.10 \
bash
说到这里讲下我之前碰的问题,我在一台服务器上搭建了jumpserver,按照官方给的部署文档,使用一键快捷部署,这个时候整个jumpserver服务是用docker给跑起来
其实用docker跑的jumpserver容器 严格意义来说叫有状态容器,那么什么叫有状态容器和无状态容器呢,解释下:
有状态容器是指在容器内部存储了重要数据,而且这些数据在容器的生命周期中是持久性的。有状态容器通常被用来运行需要持久性存储(比如数据库、缓存系统等)或者需要特定网络标识(比如需要固定 IP 地址)的应用程序。
无状态容器是指容器内部不存储关键数据,所有必要的数据都从外部服务获取。无状态容器通常用于运行无需持久性存储或者可以横向扩展(scale horizontally)的应用程序,比如 Web 服务器。
事故经过是这样的,有天我通过docker logs 容器id 查看到koko组件有大量的错误请求,于是我直接重启了koko组件,结果磁盘直接干满了,这个时候其实应该怎么排查,通知业务方关掉docker服务, 通过docker inspect命令查看容器的Mounts 有没有挂载目录,挂载卷,发现都没有,当时df -h查看情况是/目录下的merged目录直接干满了,
但是我/home目录挂了一块2t的盘,可以把容器根目录的东西都迁移过去,查看docker配置文件/etc/docker/daemon.json发现data-root配置是/var/lib/docker ,将服务器本地跟目录下/var/lib/docker 复制到/home/,将data-root配置项里的值修改为/home/docker,最后启动docker服务发现没有问题,解决
所以在部署一套服务的时候,比如用容器部署,对于有状态的服务,一定要规划好主机磁盘,该挂载的就挂载,如果是别人写好的,你不知道这些容器持久存储的内容到底都放到本机那个位置上去了,那就抓瞎了
10.Docker中的网络功能介绍
Docker允许通过外部访问容器或容器互联的方式来提供网络服务.
面试官比较喜欢问的就是docker的几种网络模式,及其各自理解,在这里我就不多说这些理论的东西了,其一,真正需要修改网络模式的场景很少,其二,理论的东西理解就可以了,没必要去死记,等真正要用到了再后头看看
外部访问容器
容器中可以运行一些网络应用,要让外部也可以访问这些应用,可以通过-p或者-P参数来指定端口映射.
当使用-P 标记时,Docker会随机映射一个端口到内部容器开放的网络端口.
使用docker container ls可以看到,本机主机的20000被映射到了容器的5000端口,此时访问本机的20000端口即可访问容器内web应用提供的界面.
$ docker run -d -P training/webapp python app.py
$ docker container ls -l
同样的,可以通过docker logs命令来查看应用的信息.
p 则可以指定要映射的端口,并且,在一个指定端口上值可以绑定一个容器,支持的格式有
ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort
映射所有接口地址
使用hostPort:containerPort格式本地的5000端口映射到容器的5000端口,可以执行
$ docker run -d -p 5000:5000 training/webapp python app.py
此时默认会绑定本地所有接口上的所有地址.
映射到指定地址的指定端口
可以使用ip:hostPort:containerPort 格式指定映射使用一个特定地址,比如localhost地址127.0.0.1
$ docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py
映射到指定地址的任意端口
使用ip::containerPort绑定localhost的任意端口到容器的5000端口,本地主机会自动分配一个端口
$ docker run -d -p 127.0.0.1::5000 training/webapp python app.py
还可以使用udp标记来指定udp端口
$ docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py
查看映射端口配置
使用docker port来查看当前映射的端口配置,也可以查看到绑定的地址
注意:
- 容器有自己的内部网络和ip地址
- -p标记可以多次使用来绑定多个端口
容器互联
如果你之前有Docker使用经验,你可能已经习惯了使用--link参数来使容器互联.
随着Docker网络的完善,强烈建议大家将容器加入自定义的Docker网络来连接多个容器,而不是使用--link参数.
新建网络
下面创建一个新的Docker网络
$ docker network create -d bridge my-net
-d参数指定Docker网络类型,有bridge,overlay,其中overlay网络类型用于Swarm mode
连接容器
运行一个容器并连接到新建的my-net网络
$ docker run -it --rm --name busybox1 --network my-net busybox sh
打开新的终端,再运行一个容器并加入到my-net网络
$ docker run -it --rm --name busybox2 --network my-net busybox sh
再打开一个新的终端查看容器信息
下面通过ping来证明busybox1容器和busybox2容器建立了互联关系,
在busybox1容器输入以下命令
用ping来测试连接busybox2容器,它会解析成172.19.0.3
同理在busybox2容器执行ping busybox1,也会成功连接到
这样,busybox1容器和busybox2容器建立了互联关系.
高级网络配置
当docker启动时,会自动在主机上创建一个docker0虚拟网桥,实际上是linux的一个bridge,可以理解为一个软件交换机,它会在挂载到它的网口之间进行转发.
同时,Docker随机分配一个本机未占用的私有网络中的一个地址给docker0接口,比如典型的172.17.42.1,掩码为255.255.0.0,此后启动的容器内的网口也会自动分配一个同一网络(172.17.0.0/16)的地址.
当创建一个Docker容器的时候,同时会创建一对veth pair接口(当数据包发送到一个接口时,另外一个接口也可以收到相同的数据包)
这对接口一端在容器内,即eth0;另外一段在本地被挂载docker0网桥,名称以veth开头通过这种方式,主机可以跟容器通信,容器之间也可以相互通信,docker就创建了再主机和所有容器之间一个虚拟共享网络.
快速配置指南
下面是一个跟docker网络相关的命名列表
其中有些命令只有在docker服务启动的时候才能配置,而且不能马上生效.
-b BRIDGE 或 --bridge=BRIDGE 指定容器挂载的网桥
--bip=CIDR 定制 docker0 的掩码
-H SOCKET... 或 --host=SOCKET... Docker 服务端接收命令的通道
--icc=true|false 是否支持容器之间进行通信
--ip-forward=true|false 请看下文容器之间的通信
--iptables=true|false 是否允许 Docker 添加 iptables 规则
--mtu=BYTES 容器网络中的 MTU
下面2个命令选项既可以在启动服务时指定,也可以在启动容器时指定.在docker服务启动的时候指定则会成为默认值,后面执行docker run时可以覆盖设置的默认值.
--dns=IP_ADDRESS... 使用指定的DNS服务器
--dns-search=DOMAIN... 指定DNS搜索域
最后这些选项只有在docker run执行时使用,因为它是针对容器的特性内容
-h HOSTNAME 或 --hostname=HOSTNAME 配置容器主机名
--link=CONTAINER_NAME:ALIAS 添加到另一个容器的连接
--net=bridge|none|container:NAME_or_ID|host 配置容器的网络模式
-p SPEC 或 --publish=SPEC 映射容器端口到宿主主机
-P or --publish-all=true|false 映射容器所有端口到宿主主机
容器访问控制
容器的访问控制,主要通过linux上的iptables防火墙来进行管理和实现,iptables是linux上默认的防火强软件,在大部分发行版中都自带.
提到这里,多说一句,咱们在启docker容器通过-p对外暴露端口的本质是啥,其实就是在iptables Docker链上临时添加多条ACCEPT规则,所以但凡是部署过docker的服务器,iptables一定不能乱动,无论是不小心清理了规则还是重启了iptables服务都会引起docker 启的容器的服务无法提供访问了,碰到这种问题,可暂时使用命令重启systemctl restart docker才能恢复
容器访问外部网络
容器要想访问外部网络,需要本地系统的转发支持,在linux系统中,检查转发是否打开.
$ sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1
如果为0,说明没有开启转发,则需要手动打开.
$ sysctl -w net.ipv4.ip_forward=1
如果在启动docker服务的时候设定--ip-forward=true,Docker就会自动设定系统的ip_forward参数为1.
需要注意的是,现在docker容器启动的端口监听有的是在ipv6,如果是ipv6的话,那么也需要将ipv6的转发也得打开
命令
echo "net.ipv6.conf.all.forwarding=1" >> /etc/sysctl.conf
sysctl -p
容器之间访问
容器之间相互访问,需要两方面的支持.
- 容器的网络拓扑是否已经互联,默认情况下,所有容器都会被连接到docker0网桥上
- 本地系统的防火强软件--iptables是否允许通过.
访问所有端口
当启动docker服务时候,默认会添加一条转发策略大iptables的FORWARD链上,策略为通过(ACCEPT) 还是禁止(DROP)取决于配置--icc=true还是--icc=false,当然,如果手动指定--iptables=false则不会添加iptables规则.
可见,默认情况下,不同容器之间是允许网络互通的,如果为了安全考虑,可以在/etc/default/docker文件中配置DOCKER_OPTS=--iccfalse来禁止它.
访问指定端口
在通过--icc=false关闭网络访问后,还可以通过--link==CONTANINER_NAME:ALIAS选项来访问容器的开放端口
映射容器端口到宿主机的实现
默认情况下,容器可以主动访问到外部网络的连接,但是外部网络无法访问到容器.
容器访问外部实现
容器所有到外部网络的连接,源地址都会被NAT成本地系统的IP地址,这是使用iptables的源地址伪装操作实现的
查看主机的NAT
$ sudo iptables -t nat -nL
...
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 !172.17.0.0/16
...
其中,上述规则将所有源地址在 172.17.0.0/16 网段,目标地址为其他网段(外部网络的流量动态伪装为从系统网卡发出。MASQUERADE 跟传统 SNAT 的好处是它能动态从网卡获取地址.
外部访问容器实现
容器允许外部访问,可以在 docker run 时候通过 -p 或 -P 参数来启用,不管用那种办法,其实也是在本地的 iptable 的 nat 表中添加相应的规则.使用 -P 时:
配置docker0网桥
Docker服务默认会创建一个docker0网桥,它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络.
Docker默认指定了docker0接口的IP地址和子网掩码,让主机和容器之间可以通过网桥相互通信,它还给出了MTU(接口允许接口的最大传输单元),通常是1500Bytes,或宿主主机网络路由上支持的默认值,这些值都可以在服务启动的时候进行配置.
也可以在配置文件中配置DOCKER_OPTS,然后重启服务.
由于目前Docker网桥是linux网桥,用户可以使用brctl show来查看网桥和端口连接信息
每次创建一个新容器的时候,Docker 从可用的地址段中选择一个空闲的IP地址分配给容器的eth0端口,使用本地主机上 docker0 接口的IP作为所有容器的默认网关.
$ sudo docker run -i -t --rm base /bin/bash
$ ip addr show eth0
24: eth0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qle
n 1000
link/ether 32:6f:e0:35:57:91 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.3/16 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::306f:e0ff:fe35:5791/64 scope link
valid_lft forever preferred_lft forever
$ ip route
default via 172.17.42.1 dev eth0
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.3
自定义网桥
除了默认的 docker0 网桥,用户也可以指定网桥来连接各个容器.在启动 Docker 服务的时候,使用 -b BRIDGE 或 --bridge=BRIDGE 来指定使用的网桥.如果服务已经运行,那需要先停止服务,并删除旧的网桥.
然后创建一个网桥bridge0
在docker配置文件/etc/docker/daemon.json中添加如下内容,即可将docker默认桥接到新创建的网桥上
{
"bridge": "bridge0",
}
启动docker服务
新建一个容器,可以看到它已经桥接到了 bridge0 上.可以继续用 brctl show 命令查看桥接的信息.另外,在容器中可以使用 ip addr 和 ip route 命令来查看 IP 地址配置和路由信息.
编辑网络配置文件
Docker 1.2.0 开始支持在运行中的容器里编辑 /etc/hosts ,/etc/hostname 和 /etc/resolve.conf 文件.但是这些修改是临时的,只在运行的容器中保留,容器终止或重启后并不会被保存下来.也不会被 docker commit 提交.
后面的内容就是关于docker三剑客了,分别是Compose,Machine,Swarm,篇幅太长了,后续再更新
标签:容器,--,nginx,讲解,镜像,Docker,docker From: https://blog.51cto.com/u_15703497/8009944