实验环境
服务器
最低配置要求:
- 2 核虚拟 CPU
- 4 GB 内存
- 20 GB 储存空间
==X 4 台==
(三台集群,一台镜像仓库服务器)
网络环境
由于搭建网络需要,同时要让各个主机互通,因此这里需要对云服务器设置专门的网络(同时也需要开放服务器对应的公网 IP)
- 在云服务器厂商中,创建 VPC 网络,网段为: 172.31.0.0/16
- 创建私有网络(交换机):172.31.0.0/24
- 所有主机均设置为在该私有网络(交换机)当中
安全组设置
-
开启组内互信
同一内网开放所有端口
-
对外开放如下端口(每台服务器均开放)
协议 端口范围 服务名称 描述 TCP 22 ssh SSH远程访问服务器 TCP 53 domain DNS服务器通信 UDP 53 domain DNS服务器通信 TCP 80 http 暴露Web应用程序或服务 TCP 111 rpcbind NFS文件共享服务通信 TCP 179 bgp BGP路由协议通信 TCP 443 https 暴露Web应用程序或服务 TCP 2379-2380 etcd Etcd集群通信 TCP 5000 docker-registry Docker镜像本地仓库通信(离线环境) TCP 5080 apt-cacher-ng 本地APT源通信(离线环境) TCP 6443 kube-api Kubernetes API服务器通信 TCP 9099-9100 calico-etcd Calico网络插件通信 TCP 10250-10258 kubelet Kubernetes主节点组件通信 TCP 30000-32767 nodeport NodePort类型的Service通信
基础整理
kubernetes集群概述
k8s以容器技术为基础,实现对容器的高效管理,安全容错,弹性伸缩等功能。docker容器技术实现,将独立应用抽象为独立虚拟主机。随着主机的增多,对主机的编排管理是一项重要工作。
组件如下:

1、控制平面组件(Control Plane Components)
控制平面的组件对集群做出全局决策(比如调度),以及检测和响应集群事件
控制平面组件可以在集群中的任何节点上运行。 然而,为了简单起见,设置脚本通常会在同一个计算机上启动所有控制平面组件, 并且不会在此计算机上运行用户容器。 请参阅 使用 kubeadm 构建高可用性集群 中关于多 VM 控制平面设置的示例。
kube-apiserver
API 服务器是 Kubernetes 控制面的组件, 该组件公开了 Kubernetes API。 API 服务器是 Kubernetes 控制面的前端。
Kubernetes API 服务器的主要实现是 kube-apiserver。 kube-apiserver 设计上考虑了水平伸缩,也就是说,它可通过部署多个实例进行伸缩。 你可以运行 kube-apiserver 的多个实例,并在这些实例之间平衡流量。
etcd
**etcd 是兼具一致性和高可用性的键值数据库,可以作为保存 Kubernetes 所有集群数据的后台数据库。**etcd 文档。
kube-scheduler
控制平面组件,负责监视新创建的、未指定运行 节点(node)的 Pods,选择节点让 Pod 在上面运行。
调度决策考虑的因素包括单个 Pod 和 Pod 集合的资源需求、硬件/软件/策略约束、亲和性和反亲和性规范、数据位置、工作负载间的干扰和最后时限。
kube-controller-manager
在主节点上运行 控制器 的组件。
从逻辑上讲,每个 控制器都是一个单独的进程, 但是为了降低复杂性,它们都被编译到同一个可执行文件,并在一个进程中运行。
这些控制器包括:
- 节点控制器(Node Controller): 负责在节点出现故障时进行通知和响应
- 任务控制器(Job controller): 监测代表一次性任务的 Job 对象,然后创建 Pods 来运行这些任务直至完成
- 端点控制器(Endpoints Controller): 填充端点(Endpoints)对象(即加入 Service 与 Pod)
- 服务帐户和令牌控制器(Service Account & Token Controllers): 为新的命名空间创建默认帐户和 API 访问令牌
cloud-controller-manager
云控制器管理器是指嵌入特定云的控制逻辑的 控制平面组件。 云控制器管理器允许您链接集群到云提供商的应用编程接口中, 并把和该云平台交互的组件与只和您的集群交互的组件分离开。
<span class="ne-text">cloud-controller-manager</span>
仅运行特定于云平台的控制回路。 如果你在自己的环境中运行 Kubernetes,或者在本地计算机中运行学习环境, 所部署的环境中不需要云控制器管理器。
与 <span class="ne-text">kube-controller-manager</span>
类似,<span class="ne-text">cloud-controller-manager</span>
将若干逻辑上独立的 控制回路组合到同一个可执行文件中,供你以同一进程的方式运行。 你可以对其执行水平扩容(运行不止一个副本)以提升性能或者增强容错能力。
下面的控制器都包含对云平台驱动的依赖:
- 节点控制器(Node Controller): 用于在节点终止响应后检查云提供商以确定节点是否已被删除
- 路由控制器(Route Controller): 用于在底层云基础架构中设置路由
- 服务控制器(Service Controller): 用于创建、更新和删除云提供商负载均衡器
2、Node 组件
节点组件在每个节点上运行,维护运行的 Pod 并提供 Kubernetes 运行环境。
kubelet
一个在集群中每个 节点(node)上运行的代理。 它保证容器(containers)都 运行在 Pod 中。
kubelet 接收一组通过各类机制提供给它的 PodSpecs,确保这些 PodSpecs 中描述的容器处于运行状态且健康。 kubelet 不会管理不是由 Kubernetes 创建的容器。
kube-proxy
kube-proxy 是集群中每个节点上运行的网络代理, 实现 Kubernetes 服务(Service) 概念的一部分。
kube-proxy 维护节点上的网络规则。这些网络规则允许从集群内部或外部的网络会话与 Pod 进行网络通信。
如果操作系统提供了数据包过滤层并可用的话,kube-proxy 会通过它来实现网络规则。否则, kube-proxy 仅转发流量本身。
将k8s集群比作公司集团,各个组件之间的关系如图所示:

控制(运行)架构
前.1.2内容主要介绍基本的对于k8s各个组件当中的认识。但是作为使用者作为汽车的驾驶人员,并不需要关心汽车的传动结构是如何设计的等等基本细节。因此作为使用人员更加关注的是,如何驾驭k8s集群。
组件分析
在开始绘制大概的架构图之前,从正常的开发经验出发来猜测如果我们需要实现类似于对docker集群的管理。我们需要注意哪些问题,并且当我们部署docker是,不同的docker容器相当于一个基础服务,不同的服务之间充当不同角色。因此我们从可用服务进行底层划分,由于按照最小(业务层面出发)可拆分服务进行划分,例如一个基础api服务为一个划分,一个基础api服务又包含多个组件,每个组件都可以由docker进行部署。
Pod
因此,我们抽象出k8s当中最基础的单元Pod
Pod:
- Pod是最小调度单元
- Pod里面会包含一个或多个容器(Container)
- Pod内的容器共享存储及网络,可通过localhost通信
于是在k8s当中,最小的执行单元将不再是容器,这非常好理解,一块cpu当中具有多个核心,但是在售卖时,我们往往按照一块一块cpu进行售卖,而不是按照一个核心一个核心进行售卖。通过不同的cpu型号来划分不同的核心配置。
Deployment
此时我们的问题从对docker容器的管理转移到了对Pod的管理。
因此Deployment 是在 Pod 这个抽象上更为上层的一个抽象,可以定义一组 Pod 的副本数目、以及这个 Pod 的版本。一般大家用 Deployment 这个抽象来做应用的真正的管理,而 Pod 是组成 Deployment 最小的单元
- 定义一组Pod的副本数量,版本等
- 通过控制器维护Pod的数目
- 自动恢复失败的Pod
- 通过控制器以指定的策略控制版本
Service
此时我们解决了关于Pod的管理问题,但是Pod做为一个最小可调度服务,因此不同的Pod可能存在调用关系。以Java微服务举例,需要通过网络,通过注册中心完成api间的服务调用。Service解决的就是Pod的服务调用问题。
并且Pod是不稳定的,IP是会变化的,所以也需要一层抽象来屏蔽这种变化
- 提供访问一个或者多个Pod实例稳定的访问地址
- 支持多种访问方式ClusterIP(对集群内部访问)NodePort(对集群外部访问)LoadBalancer(集群外部负载均衡)

Ingress
一个Service可用解决多个Pod之间的通讯问题,但是对于Service来说,以及服务的复杂度,可能需要多个Service来进行处理,因此还需要一个抽象层对Service进行管理,那么这个层面就是Ingress。同时ingress提供对外暴露接口,其本质就是一个nginx服务器。
其中Ingress与Service的关系,类比微服务架构如下图所示:
存储
由于Pod本质上还是基于Docker进行实现的,因此对于持久化操作来说依然可用采用原先基于Docker的存储方式。但是对于k8s集群来说,对于一个统一管理平台来说同样可以由一个抽象层进行统一管理,此时我们集成了三要素:网络抽象,运行抽象,存储抽象。同时这里也向我们传达了一个基本的设计思想,代理模式,通过对底层的抽象,向上层提供更加符合业务操作的服务。在底层是现实Pod依然通过docker的卷挂载实现,但是对k8s操作来说我们可以直接通过抽象存储层进行操作。
那么这个抽象,在这里是:PV&PVC。
PV:持久卷(Persistent Volume),将应用需要持久化的数据保存到指定位置
PVC:持久卷申明(Persistent Volume Claim),申明需要使用的持久卷规格
同时在声明之后,持久层的内容依然需要存储到服务器物理硬盘当中,那么由于良好的抽象层设计,同时为了保证存储的安全,这里依然可以在物理存储层面采用分布式存储器来确保数据安全。
因此,在存储层面,在配合k8s集群时,可以由nfs文件系统来完成物理层面的文件存储。
这里需要注意:PVC其实在Pod和PV之前又增加了一层抽象,这样做的目的在于将Pod的存储行为于具体的存储设备解耦,试想一下,假设哪天NFS网络存储的IP地址变化了,如果没有PVC,就需要每个Pod都改一下IP的声明,那得多累,有PVC来屏蔽这些细节之后只用改PV即可!他们是动态分配的
此外,在存储时,有个特殊的存储需求,那就是对配置文件的存储需求,对于配置文件来说,我们可能希望,当配置文件修改时,配置文件将可以及时生效。因此在k8s当中有个configMap的概念。
NameSpace
之后,我们这里你会发现,这样一个关系。那就是,我们实际上在"干活"的组件是,Pod。为了管理Pod我们有了deployment,同时Pod的运行需要网络,于是又Service,对网络进一层管理于是有了Ingress。之后是对存储的需求,于是又提出了Pv与Pvc,其中Pv面向底层的物理存储进行定义操作,Pvc面向Pod。那么在这里,这些都是我们一个正常的服务必要的组件,那么假设我有另一个服务,另一个独立的系统需要管理,那么这个时候就显然需要对我们上述提到的内容进行隔离管理。那么这个就是我们的NameSpace,当没有指定NameSpace是,所有声明的资源都在默认NameSpace当中。当然这里不同的是:持久化存储卷和持久化存储卷声明通常属于集群范围的资源,而不受命名空间的限制。因此,在上述提供的配置中,并没有指定命名空间是正常的。持久化存储通常被设计为跨命名空间共享,以便多个 Pod 可以访问同一块持久化存储。换句话说,在这里存储被设计为整个集群的资源。
梳理
因此对于k8s我们可以做一个简单的梳理,在屏蔽底层细节的同时,如果我们需要将服务进行大规模推送至k8s统一管理平台,我们需要注意哪些细节。
服务层面
在服务层面,我们需要关注到Pod,我们需要定义并且声明一个Pod,同时我们需要关注到,我们要部署多少个Pod,这些Pod将有什么特殊特性。例如,你是否希望此时运行的Pod(因为我们知道Pod实际上还是个容器(同时由于Pod本身做了一层抽象,因此对于容器的实现不一定需要采用docker进行实现,实际上很早以前,k8s就有想要拜托docker的想法))因此,此时我们需要关注到Deployment。当然在一开始时我们还需要主要到NameSpace。
一个标准的服务示例可以是这样的:
(注意在k8s当中,可以直接使用指令创建资源,也可以直接使用yml文件,通过kubectl apply -f deployment.yaml
即可,这一点与docker的docker-compose类似)
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: 示例deployment
namespace: 示例空间
spec:
replicas: 2
selector:
matchLabels:
app: 示例应用
template:
metadata:
labels:
app: 示例应用
spec:
containers:
- name: 示例容器
image: 示例镜像
volumeMounts:
- name: 示例pvc
mountPath: /data
volumes:
- name: 示例pvc
persistentVolumeClaim:
claimName: 示例pvc
restartPolicy: Always
hostname: 示例pod
---
apiVersion: v1
kind: Service
metadata:
name: 示例服务
namespace: 示例空间
spec:
selector:
app: 示例应用
ports:
- name: http
port: 80
targetPort: 8080
type: ClusterIP
网络
之后是我们需要关注到网络层面。也就是我们的Pod将使用哪个网络,这里值得一提的是,在内部网络当中,是支持通过生成的域名进行访问的。也就是说,一个组内网络是互通的,并且在Service当中具备负载均衡。与传统的Java微服务架构当中的Nacos来说,Service少了服务之间api级别的服务发现。当然为了弥补这个缺陷,k8s当中拥有:Istio 这一类服务发现技术。这意味着我们可以不在需要nacos的注册中心功能,同时基于configMap,我们也可以取代注册中心的功能。当然对于传统微服务来说我们依然可以将Nacos,ZooKeeper,并将其部署至集群,但是的确会增加服务成本,当然对于老项目又会增加重构成本,如何取舍是个需要考虑的问题。但毫无疑问,云原生体系下的技术可以将开发技术聚焦到服务数据层面,而不是语言实现层面,尽管Nacos,Zookeeper也面向分布式,但是对不同语言的项目存在一定的适配问题,基于nacos-python-sdk存在心跳检测的异常,导致nacos认为服务异常,需要手动重新实现heartbeat。
存储
存储主要聚焦到物理层的Pvc定义和Pv定义。同时需要注意到configMap如何定义编写即可。
至此,关于K8s的大致组件,在使用过程当中,作为使用者需要关注的部分为上述部分。
Docker基本使用
由于当中Pod当中需要使用到docker,因此,我们需要对Docker的基本使用重新做一个梳理。
概述
Docker 是一种轻量级的容器化技术,它可以将应用程序及其依赖项打包在一个可移植的容器中,并在任何地方运行。Docker 的实现原理主要涉及以下几个方面:
- 命名空间和控制组:Docker 利用 Linux 操作系统提供的命名空间和控制组技术来实现容器隔离。命名空间允许容器拥有自己的进程树、网络接口、用户 ID 等系统资源,而控制组则可以限制容器的 CPU、内存、磁盘等使用量。
- 镜像和容器:在 Docker 中,镜像是应用程序及其依赖项的打包格式,类似于虚拟机中的镜像文件,但比虚拟机更轻量级。容器则是基于镜像创建的运行实例,每个容器都是相互独立的,具有自己的文件系统、进程、网络等环境。
- Docker 守护进程:Docker 守护进程负责管理容器的生命周期,包括创建、启动、停止、删除等操作。当用户运行一个容器时,Docker 守护进程会根据该容器所需的镜像创建一个新的容器,并为其分配唯一的命名空间和控制组。
- Dockerfile:Dockerfile 是用于定义镜像构建过程的文本文件,其中包含了一系列指令,如基础镜像选择、软件包安装、环境变量设置等。当用户执行 Docker build 命令时,Docker 会根据 Dockerfile 构建一个新的镜像。
通过上述机制,Docker 实现了应用程序和其依赖项的隔离和可移植性,并为用户提供了简单易用的管理工具。同时,Docker 还支持容器编排和集群管理等高级功能,可以帮助用户更好地构建分布式系统。
(Ps: 在使用Docker启动容器时,请勿在root用户权限下运行容器,实验环境可以采用。因为docker本质上只是一个程序,底层还是基于Linux操作系统提供的命名空间进行,控制主,文件系统上实现的(是的在操作系统成本上的抽象)。因此当docker以root身份运行时,如果攻击者成功入侵了容器,并获取了 root 权限,那么他们可以对容器内的文件系统进行修改、进行系统级别的操作,甚至尝试逃逸到宿主机系统。这对整个平台来说是灾难性的)
docker的基本架构如下:
-
Docker_Host:
- 安装Docker的主机
-
Docker Daemon:
- 运行在Docker主机上的Docker后台进程
-
Client:
- 操作Docker主机的客户端(命令行、UI等)
-
Registry:
- 镜像仓库
- Docker Hub
-
Images:
- 镜像,带环境打包好的程序,可以直接启动运行
-
Containers:
- 容器,由镜像启动起来正在运行中的程序
安装
-
移除以前的安装包:
ymlsudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine
-
配置yml源
ymlsudo yum install -y yum-utils sudo yum-config-manager \ --add-repo \ http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
-
安装社区版本docker
ymlsudo yum install -y docker-ce docker-ce-cli containerd.io
#以下是在安装k8s的时候使用 yum install -y docker-ce-20.10.7 docker-ce-cli-20.10.7 containerd.io-1.4.6
-
开启开机自启动并启动容器
ymlsystemctl enable docker --now
此外还可以配置镜像加速地址(阿里云有相关服务,只需要将地址进行替换即可)
"registry-mirrors": ["82m9ar63.mirror.aliyuncs.com"] 是示例地址。
yml
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://82m9ar63.mirror.aliyuncs.com"],
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2"
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
此外还有docker-compose
yml
sudo curl -L https://github.com/docker/compose/releases/download/v2.21.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
常用命令
docker的运行指令关系,可以使用一张图进行概括:
对镜像进行操作
yml
docker pull nginx #下载最新版
镜像名:版本名(标签)
docker pull nginx:1.20.1
docker pull redis #下载最新
docker pull redis:6.2.4
## 下载来的镜像都在本地
docker images #查看所有镜像
redis = redis:latest
docker rmi 镜像名:版本号/镜像id
对容器进行操作
yml
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
【docker run 设置项 镜像名 】 镜像启动运行的命令(镜像里面默认有的,一般不会写)
# -d:后台运行
# --restart=always: 开机自启
docker run --name=mynginx -d --restart=always -p 88:80 nginx
# 查看正在运行的容器
docker ps
# 查看所有
docker ps -a
# 删除停止的容器
docker rm 容器id/名字
docker rm -f mynginx #强制删除正在运行中的
#停止容器
docker stop 容器id/名字
#再次启动
docker start 容器id/名字
#应用开机自启
docker update 容器id/名字 --restart=always
进入容器内部
yml
docker exec -it 容器id /bin/bash
挂载数据卷
yml
docker run --name=mynginx \
-d --restart=always \
-p 88:80 -v /data/html:/usr/share/nginx/html:ro \
nginx
# 修改页面只需要去 主机的 /data/html
其余指令可参考运行指令关系图
搭建私有仓库/上传本地镜像至仓库
在实际任务需求当中,我们存在搭建私有仓库,或者使用阿里云仓库,再或者在dockerhub使用仓库的需求。尤其是在DevOps过程中,我们构建本地镜像时需要推送至仓库,之后拉取到测试/生产环境当中的最新镜像进行部署。并且在整个项目周期当中,会产生较多的镜像,需要进行统一托管。
搭建私有仓库
这里使用官方的register进行搭建,并带有基本界面
通过docker-compose进行搭建
yml
version: '3.0'
services:
registry:
image: registry
volumes:
- ./registry-data:/var/lib/registry
ui:
image: joxit/docker-registry-ui:static
ports:
- 8080:80
environment:
- REGISTRY_TITLE=测试仓库
- REGISTRY_URL=http://registry:5000
depends_on:
- registry
docker-compose up -d
这将启动私有仓库服务,并且 -d
参数表示在后台运行服务。
验证私有仓库是否成功运行:
docker-compose ps
停止并移除私有仓库服务:
docker-compose down
之后需要配置信任地址:
vi /etc/docker/daemon.json
添加地址
例如:
yml
{
"registry-mirror":[
"http://hub-mirror.c.163.com"
],
"insecure-registries":[
"192.168.1.1:5000"
]
}
之后重启docker
yml
systemctl daemon-reload
systemctl restart doc
推送
推送镜像到私有镜像服务必须先tag,步骤如下:
① 重新tag本地镜像,名称前缀为私有仓库的地址:192.168.150.101:8080/
bash
docker tag nginx:latest 192.168.150.101:8080/nginx:1.0
② 推送镜像
bash
docker push 192.168.150.101:8080/nginx:1.0
③ 拉取镜像
bash
docker pull 192.168.150.101:8080/nginx:1.0
(注意在dockerhub创建仓库类似(并且可以搭建私有仓库,此时需要docker login 登录,如果是私有仓库))
yml
docker login <registry-url>
echo "<your-password>" | docker login --username <your-username> --password-stdin <registry-url>
docker-compose结构
Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。格式如下:
json
version: "3.8"
services:
mysql:
image: mysql:5.7.25
environment:
MYSQL_ROOT_PASSWORD: 123
volumes:
- "/tmp/mysql/data:/var/lib/mysql"
- "/tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf"
restart: always
web:
build: .
ports:
- "8090:8090"
- mysql:一个基于
mysql:5.7.25
镜像构建的容器,并且挂载了两个目录 - web:一个基于
docker build
临时构建的镜像容器,映射端口时8090
可以看做是将多个docker run命令写到一个文件,只是语法稍有差异。描述清楚需求,可以由gpt生成。or 使用shell脚本
shell
#!/bin/bash
# 交互式输入服务个数
read -p "请输入要创建的服务个数: " num_services
output_file="docker-compose.yml"
# 创建空的 Docker Compose 文件
echo "version: '3.8'" > $output_file
echo "services:" >> $output_file
# 循环读取每个服务的信息并写入文件
for ((i=1; i<=$num_services; i++))
do
read -p "请输入第 $i 个服务的名称: " service_name
read -p "请输入基础镜像名称: " image_name
read -p "请输入挂载卷信息(格式:/path/to/host/dir:/container/dir): " volume_info
read -p "是否自动启动该服务?(true/false): " auto_start
# 写入服务信息到 Docker Compose 文件
echo " $service_name:" >> $output_file
echo " image: $image_name" >> $output_file
echo " container_name: $service_name" >> $output_file
echo " volumes:" >> $output_file
echo " - $volume_info" >> $output_file
echo " networks:" >> $output_file
echo " - bridge" >> $output_file
echo " restart: $auto_start" >> $output_file
done
echo "networks:" >> $output_file
echo " bridge:" >> $output_file
echo " driver: bridge" >> $output_file
echo "Docker Compose 文件已生成: $output_file"
镜像构建
镜像构建是docker当中非常重要的部分
在根目录下使用Dockerfile
语法与格式
格式如下:
FROM(指定基础 image)
构建指令,必须指定且需要在 Dockerfile 其他指令的前面。后续的指令都依赖于该指令指定的 image。FROM 指令指定的基础 image 可以是官方远程仓库中的,也可以位于本地仓库
sql
FROM centos:7.2
FROM centos
MAINTAINER(用来指定镜像创建者信息)
构建指令,用于将 image 的制作者相关的信息写入到 image 中。当我们对该 image 执行 docker inspect 命令时,输出中有相应的字段记录该信息。
perl
MAINTAINER wangyang "[email protected]"
LABEL(将元数据添加到镜像)
标签的定义
ini
# 指令将元数据添加到镜像。`LABEL` 是键值对。要在 `LABEL` 值中包含空格,请像在命令行中一样使用引号和反斜杠
LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."
# 多行标签定义方式
LABEL multi.label1="value1" multi.label2="value2" other="value3"
LABEL multi.label1="value1" \
multi.label2="value2" \
other="value3"
标签的继承
**基础或父镜像(**FROM
行中的镜像)中包含的标签由您的镜像继承。如果标签已经存在但具有不同的值,则最近应用的值将覆盖任何先前设置的值
查看镜像标签
ini
$ docker image inspect --format='' myimage
案例
ini
# dockerfile
FROM busybox
LABEL author=wangyanglinux
CMD echo wangyanglinux
# 构建镜像
[root@k8s-master01 testd]# docker build -t wangyanglinux:0.0.1 . --no-cache
# 安装 jq
[root@k8s-master01 testd]# yum install epel-release
[root@k8s-master01 testd]# yum install jq
# 查看标签
[root@k8s-master01 testd]# docker image inspect wangyanglinux:0.0.1 --format "{{json .ContainerConfig.Labels}}" | jq
{
"author": "wangyanglinux"
}
RUN(安装软件用)
构建指令,RUN 可以运行任何被基础 image 支持的命令。如基础 image 选择了 Centos,那么软件管理部分只能使用 Centos 的包管理命令
bash
RUN cd /tmp && curl -L 'http://archive.apache.org/dist/tomcat/tomcat-7/v7.0.8/bin/apache-tomcat-7.0.8.tar.gz' | tar -xz
RUN ["/bin/bash", "-c", "echo hello"]
USER(设置container容器的用户)
设置指令,设置启动容器的用户,默认是 root 用户
ini
USER daemon = ENTRYPOINT ["memcached", "-u", "daemon"]
EXPOSE(指定容器需要映射到宿主机器的端口)
设置指令,该指令会将容器中的端口映射成宿主机器中的某个端口。当你需要访问容器的时候,可以不是用容器的 IP 地址而是使用宿主机器的 IP 地址和映射后的端口。要完成整个操作需要两个步骤,首先在 Dockerfile 使用 EXPOSE 设置需要映射的容器端口,然后在运行容器的时候指定 -P 选项加上 EXPOSE 设置的端口,这样 EXPOSE 设置的端口号会被随机映射成宿主机器中的一个端口号。也可以指定需要映射到宿主机器的那个端口,这时要确保宿主机器上的端口号没有被使用。EXPOSE指令可以一次设置多个端口号,相应的运行容器的时候,可以配套的多次使用 -p 选项
arduino
# 映射多个端口
EXPOSE port1 port2 port3
# 随机暴露需要运行的端口
docker run -P image
# 相应的运行容器使用的命令
docker run -p port1 -p port2 -p port3 image
# 还可以指定需要映射到宿主机器上的某个端口号
docker run -p host_port1:port1 -p host_port2:port2 -p host_port3:port3 image
ENV(用于设置环境变量)
构建指令,在 image 中设置一个环境变量。设置了后,后续的 RUN 命令都可以使用,container 启动后,可以通过 docker inspect 查看这个环境变量,也可以通过在 docker run --env key=value 时设置或修改环境变量。假如你安装了 JAVA 程序,需要设置 JAVA_HOME,那么可以在 Dockerfile 中这样写:
bash
ENV JAVA_HOME /path/to/java/dirent
ARG(设置变量)
起作用时机
- arg 是在 build 的时候存在的, 可以在 Dockerfile 中当做变量来使用
- env 是容器构建好之后的环境变量, 不能在 Dockerfile 中当参数使用
案例
bash
# Dockerfile
FROM redis:3.2-alpine
LABEL maintainer="[email protected]"
ARG REDIS_SET_PASSWORD=developer
ENV REDIS_PASSWORD ${REDIS_SET_PASSWORD}
VOLUME /data
EXPOSE 6379
CMD ["sh", "-c", "exec redis-server --requirepass \"$REDIS_PASSWORD\""]
bash
FROM nginx:1.13.1-alpine
LABEL maintainer="[email protected]"
RUN mkdir -p /etc/nginx/cert \
&& mkdir -p /etc/nginx/conf.d \
&& mkdir -p /etc/nginx/sites
COPY ./nginx.conf /etc/ngixn/nginx.conf
COPY ./conf.d/ /etc/nginx/conf.d/
COPY ./cert/ /etc/nginx/cert/
COPY ./sites /etc/nginx/sites/
ARG PHP_UPSTREAM_CONTAINER=php-fpm
ARG PHP_UPSTREAM_PORT=9000
RUN echo "upstream php-upstream { server ${PHP_UPSTREAM_CONTAINER}:${PHP_UPSTREAM_PORT}; }" > /etc/nginx/conf.d/upstream.conf
VOLUME ["/var/log/nginx", "/var/www"]
WORKDIR /usr/share/nginx/html
<!--这里的变量用的就是 ARG
而不是 ENV
了,因为这条命令运行在 Dockerfile
当中的, 像这种临时使用一下的变量没必要存环境变量的值就很适合使用 ARG
-->
ADD(从 src 复制文件到 container 的 dest 路径)
xml
ADD <src> <dest>
<src> 是相对被构建的源目录的相对路径,可以是文件或目录的路径,也可以是一个远程的文件 url;
<dest> 是 container 中的绝对路径
COPY (从 src 复制文件到 container 的 dest 路径)
xml
COPY <src> <dest>
VOLUME(指定挂载点)
设置指令,使容器中的一个目录具有持久化存储数据的功能,该目录可以被容器本身使用,也可以共享给其他容器使用。我们知道容器使用的是 AUFS,这种文件系统不能持久化数据,当容器关闭后,所有的更改都会丢失。当容器中的应用有持久化数据的需求时可以在 Dockerfile中 使用该指令
css
FROM base
VOLUME ["/tmp/data"]
WORKDIR(切换目录)
设置指令,可以多次切换(相当于cd命令),对RUN,CMD,ENTRYPOINT生效
bash
WORKDIR /p1
RUN touch a.txt
WORKDIR p2
RUN vim a.txt
CMD(设置 container 启动时执行的操作)
设置指令,用于 container 启动时指定的操作。该操作可以是执行自定义脚本,也可以是执行系统命令。该指令只能在文件中存在一次,如果有多个,则只执行最后一条
bash
CMD echo "Hello, World!"
CMD /usr/local/nginx/sbin/nginx tail -f /usr/local/nginx/logs/access.log
ENTRYPOINT(设置container启动时执行的操作)
设置指令,指定容器启动时执行的命令,可以多次设置,但是只有最后一个有效。
bash
ENTRYPOINT ls -l
该指令的使用分为两种情况,一种是独自使用,另一种和 CMD 指令配合使用。当独自使用时,如果你还使用了 CMD 命令且 CMD 是一个完整的可执行的命令,那么 CMD 指令和 ENTRYPOINT 会互相覆盖只有最后一个 CMD 或者 ENTRYPOINT 有效
bash
CMD echo "Hello, World!"
ENTRYPOINT ls -l
# CMD 指令将不会被执行,只有 ENTRYPOINT 指令被执行
ENTRYPOINT ls -l
CMD echo "Hello, World!"
# 反之
另一种用法和 CMD 指令配合使用来指定 ENTRYPOINT 的默认参数,这时 CMD 指令不是一个完整的可执行命令,仅仅是参数部分;ENTRYPOINT 指令只能使用 JSON 方式指定执行命令,而不能指定参数
css
FROM ubuntu
CMD ["-l"]
ENTRYPOINT ["/usr/bin/ls"]
CMD 与 ENTRYPOINT 的较量
**官方释义:**docs.docker.com/engine/refe...
cmd 给出的是一个容器的默认的可执行体。也就是容器启动以后,默认的执行的命令
arduino
FROM centos
CMD echo "hello cmd!"
docker run xx
==> hello cmd!
# 如果我们在 run 时指定了命令或者有entrypoint,那么 cmd 就会被覆盖。仍然是上面的 image。run 命令变了:
docker run xx echo glgl
==> glgl
cmd 是默认体系,entrypoint 是正统地用于定义容器启动以后的执行体
less
FROM centos
CMD ["p in cmd"]
ENTRYPOINT ["echo"]
[root@k8s-master01 testd]# docker run --name test1 ent:v1
p in cmd
[root@k8s-master01 testd]# docker run --name test2 ent:v1 p in run
p in run
# ENTRYPOINT shell 模式任何 run 和 cmd 的参数都无法被传入到 entrypoint 里。官网推荐第一种用法
FROM centos
CMD ["p in cmd"]
ENTRYPOINT echo
ONBUILD(在子镜像中执行)
ONBUILD 指定的命令在构建镜像时并不执行,而是在它的子镜像中执行
bash
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
STOPSIGNAL signal
STOPSIGNAL 指令设置将发送到容器以退出的系统调用信号。这个信号可以是一个有效的无符号数字,与内核的 syscall
表中的位置相匹配,例如9
,或者是SIGNAME
格式的信号名,例如:SIGKILL
less
SIGHUP 1 A 终端挂起或者控制进程终止
SIGINT 2 A 键盘中断(如break键被按下)
SIGQUIT 3 C 键盘的退出键被按下
SIGILL 4 C 非法指令
SIGABRT 6 C 由abort(3)发出的退出指令
SIGFPE 8 C 浮点异常
SIGKILL 9 AEF Kill信号
SIGSEGV 11 C 无效的内存引用
SIGPIPE 13 A 管道破裂: 写一个没有读端口的管道
SIGALRM 14 A 由alarm(2)发出的信号
SIGTERM 15 A 终止信号
SIGUSR1 30,10,16 A 用户自定义信号1
SIGUSR2 31,12,17 A 用户自定义信号2
SIGCHLD 20,17,18 B 子进程结束信号
SIGCONT 19,18,25 进程继续(曾被停止的进程)
SIGSTOP 17,19,23 DEF 终止进程
SIGTSTP 18,20,24 D 控制终端(tty)上按下停止键
SIGTTIN 21,21,26 D 后台进程企图从控制终端读
SIGTTOU 22,22,27 D 后台进程企图从控制终端写
SHELL (覆盖命令的shell模式所使用的默认 shell)
Linux 的默认shell是 ["/bin/sh", "-c"],Windows 的是 ["cmd", "/S", "/C"]。SHELL 指令必须以 JSON 格式编写。SHELL 指令在有两个常用的且不太相同的本地 shell:cmd 和 powershell,以及可选的 sh 的 windows 上特别有用
HEALTHCHECK (容器健康状况检查命令)
ini
HEALTHCHECK [OPTIONS] CMD command
[OPTIONS] 的选项支持以下三中选项
--interval=DURATION 两次检查默认的时间间隔为 30 秒
--timeout=DURATION 健康检查命令运行超时时长,默认 30 秒
--retries=N 当连续失败指定次数后,则容器被认为是不健康的,状态为 unhealthy,默认次数是3
CMD后边的命令的返回值决定了本次健康检查是否成功,具体的返回值如下:
0: success - 表示容器是健康的
1: unhealthy - 表示容器已经不能工作了
2: reserved - 保留值
HEALTHCHECK NONE
# 第一个的功能是在容器内部运行一个命令来检查容器的健康状况
# 第二个的功能是在基础镜像中取消健康检查命令
# 为了帮助调试失败的探测,command 写在 stdout 或 stderr 上的任何输出文本(UTF-8编码)都将存储在健康状态中,并且可以通过 docker inspect 进行查询。 这样的输出应该保持简短(目前只存储前4096个字节)
注意
HEALTHCHECK 命令只能出现一次,如果出现了多次,只有最后一个生效
模板
bash
HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1
查看容器的健康状态
lua
$ docker inspect --format '{{json .State.Health.Status}}' cID
常用与示例
相对来说,完整的构建指令较为复杂,我们只需要知道常用的即可,并且知道如何修改模板,或者使用GPT修改即可。尽可能降低这方面的繁琐细节,提高工作效率。
构建Java镜像
shell
# 指定基础镜像
FROM centos:7.9.2009
# 更换 yum 源为阿里云
RUN mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup && \
curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo && \
yum clean all && \
yum makecache
# 更新系统软件包
RUN yum update -y
# 安装必要的软件包
RUN yum install -y java-1.8.0-openjdk-devel
# 设置Java环境变量
ENV JAVA_HOME /usr/lib/jvm/java-1.8.0-openjdk
ENV PATH $PATH:$JAVA_HOME/bin
# 设置工作目录
WORKDIR /app
# 拷贝整个项目到工作目录
COPY . /app
# 安装maven
RUN yum install -y maven
# 构建Java Maven项目
RUN mvn clean package
# 拷贝生成的jar包到当前目录
RUN cp /app/target/*.jar /app/docker-demo.jar
# 暴露端口
EXPOSE 8090
# 入口,java项目的启动命令
CMD ["java", "-jar", "docker-demo.jar"]
当然基础镜像我们依然使用其他的镜像,例如Java镜像,这样不需要在构建时安装Java,配置系统源等等操作,或者你可以构建自己的模板镜像。