今天希望通过这篇文章, 讲清楚目前我们都在使用的容器, K8s都在解决哪些方面的问题. 我会带着大家一起回顾从理机时代到云原生时代的演进过程, 当我们着眼于现在有哪些的时候, 可以思考一下为什么会这样发展, 新出现的技术具体在解决哪方面的问题, 这样更便于理解整体的技术演进过程. 我会从各个时代的特点演进开始分析, 对比每个时代为什么要产生了新的技术.
物理机
在物理机时代, 多个应用程序跑在一台机器上. 当我们要部署一个应用, 会在PC物理机搭建环境, 手动打包应用, 再手动启动应用, 这一系列操作都是手动的, 相当麻烦.
物理机时代的弊端
- 部署成本慢
- 成本高
- 资源浪费
- 难于扩展与迁移
- 受限于硬件
- 没有很好的隔离策略
虚拟化
通过某种方式隐藏底层物理硬件的过程, 让多个操作系统可以透明的使用和共享它. 对物理资源进行抽象.
两种虚拟化技术比较
Type-1 原生虚拟化技术(native or bare-metal hypervisors)
第一种Hypervisor是直接跑在宿主机上面作为操作系统的,特点是需要硬件支持、程序作为操作系统运行、效率高.客户机操作系统跑在上面对底层资源的访问都会被Hypervisor拦截,由它代为操作并返回结果,从而实现对系统资源的隔离.采用这种类型的虚拟机软件有KMV, VMware ESXi、 Xen等.
Type-2 宿主虚拟化技术(hosted hypervisors)
第二种Hypervisor是作为应用程序跑在操作系统上的,客户端操作系统跑在他上面所有访问也会被拦截,由于Hypervisor不直接访问硬件资源,因此运行效率通常比第一种低.采用这种类型的虚拟机软件有VMware Workstation、VirtualBox等.
容器化
容器化技术究竟解决了什么问题: 在我电脑上明明跑得很溜, 为什么在你那就不对了.
依托于容器化技术来实现:
- 要比虚拟机做得更“轻”
- 要比虚拟机做得更“快”
- 功能也要更强,比如:标准化的迁移方式、统一的参数配置、自动化部署、应用集群监控、开发与运维之间的沟通桥梁 等等.
下图是 “虚拟化技术” 与 “容器化技术” 的对比:
Docker
基础概念
Docker可以说是云原生技术的的基础. 主要用途,目前有三大类.
- 环境的一致性 - 比如,本地测试他人的软件、持续集成的时候提供单元测试和构建的环境.
- 弹性 - 因为Docker容器可以随开随关,很适合动态扩容和缩容.
- 微服务 - 通过多个容器,一台机器可以跑多个服务,因此在本机就可以模拟出微服务架构.
我们一起了解下Docker的基本概念:
- 镜像(Image)
- 镜像就是一个只读的模板,可以通过这个模板创建容器服务,一个镜像可以创建多个容器(最终服务运行或者项目运行就是在容器中)
- 容器(Container)
- Docker利用容器技术,独立运行的一个或一组应用.容器是用镜像创建的运行实例.
- 它可以被启用,开始,停止,删除. 每个容器都是相互隔离的,保证安全的平台.
- 可以把容器看作是一个简易版的Linux系统(包括root用户权限,进程空间,用户空间和网络空间等)和运行在其中的应用程序.
- 容器的定义和镜像几乎一模一样,也是一堆层的统一视角,唯一区别在于容器的最上面那一层是可读可写的
- 仓库(Repository)
- 仓库是集中存放镜像文件的场所.
- 仓库和仓库注册服务器(Registry)是有区别的,仓库注册服务器上往往存放着多个仓库,每个仓库中又包含了多个镜像,每个镜像有不同的标签
- 仓库分为公开仓库(public)和私有仓库(private)两种形式
- 最大的开放仓库是国外的 Docker Hub,存放了数量庞大的镜像供用户下载.
- 国内的公开仓库包括阿里云,网易云都有容器服务器(需要配置镜像加速)
Dockerfile
记录一个镜像的制作过程, 一个可执行的脚本. 使用Dockefile我们就可以根据文件来进行镜像的制作和生成.
# 打包段使用go:v1.0.0为基础进行编译
FROM dockerhub.xxx.com/build/go:v1.0.0 AS builder
WORKDIR /src/
COPY . /src/
RUN mkdir /src/build/config && \
# 拷贝配置文件, 执行文件
cp -rf config/* /src/build/config/ && \
cp startup.sh /src/build/ && \
# 获取gitcommit id
git rev-parse --short=8 HEAD &&\
# 配置相关环境依赖
go env -w GO111MODULE=on && \
go env -w GOPROXY=https://goproxy.cn,direct && \
go env -w GOPRIVATE=gitlab.idc.xxx.com && \
# 编译二进制文件
go build -o /src/build/app /src/cmd/apiserver/main.go && \
go build -o /src/build/cron /src/cmd/cron/main.go
###############################################################
# 确认运行阶段相关参数
FROM dockerhub.xxx.com/runtime/golang:v1.0.1
WORKDIR /home/www/
# 拷贝编译后文件
COPY --from=builder /src/build/ /home/www/
# 创建日志文件夹, 添加相关权限
RUN mkdir /home/www/log && chmod +x /home/www/startup.sh
# 暴露80端口
EXPOSE 80
# 执行startup.sh(exec ./app -c config/app.yaml)
ENTRYPOINT ["/home/www/startup.sh"]
Docker-Compose
对比Dockerfile记录了单个镜像的构建过程, docker-compose记录 一个项目(一般是多个镜像)的构建过程. Docker-Compose 是 Docker 的一种编排服务,是一个用于在 Docker 上定义并运行复杂应用的工具,可以让用户在集群中部署分布式应用.
通过 Docker-Compose 用户可以很容易地用一个配置文件定义一个多容器的应用,然后使用一条指令安装这个应用的所有依赖,完成构建. Docker-Compose 解决了容器与容器之间如何管理编排的问题.
Docker-compose 使用的三个步骤:
- 使用 Dockerfile 定义应用程序的环境.
- 使用 docker-compose.yml 定义构成应用程序的服务,这样它们可以在隔离环境中一起运行.
- 最后,执行 docker-compose up 命令来启动并运行整个应用程序.
version: "3.9"
services:
java:
build: .
image: java:0.1
ports:
- 8080:8080
depends_on:
- mysql
- redis
mysql:
image: mysql:5.7
restart: always
ports:
- 3306:3306
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: java
volumes:
- ./sql-init/:/docker-entrypoint-initdb.d/
redis:
image: redis:6.2.5
restart: always
ports:
- 6379:6379
nginx:
image: nginx:1.21.1
restart: always
ports:
- 80:80
volumes:
- ./html:/usr/share/nginx/html
云原生
k8s
dockerfile -> docker-compose -> k8s/docker swarm
一个镜像 -> 一个项目(多个镜像)-> 多个项目
在Docker持续上升的时候, 谷歌在2014年横空杀出, 推出基于内部Borg开源软件Kubernetes. Kubernetes和Swarm都承诺可帮助用户管理容器,但谷歌的软件开源免费又可靠,很快成为了一统江湖的首选工具.
Kubernetes 是一个可移植、可扩展的开源平台,用于管理容器化的工作负载和服务,可促进声明式配置和自动化. Kubernetes 拥有一个庞大且快速增长的生态,其服务、支持和工具的使用范围相当广泛.
我们再回顾一下各个阶段:
- 传统部署 - 资源无法有效的控制, 资源利用率不高, 剩余资源无法有效分配
- 虚拟化部署 - 通过VM之间隔离, 需要虚拟化系统
- 容器部署 - 比VM更加轻量级, 与基础架构完全隔离
Kubernetes,构建在 Docker 技术之上,为跨主机的容器化应用提供资源调度、服务发现、高可用管理和弹性伸缩等一整套功能,它提供了完善的管理工具,涵盖开发、部署测试、运维监控等各个环节.它的目标不仅仅是一个编排系统,更是提供一个规范,可以让你来描述集群的架构,定义服务的最终状态,Kubernetes可以帮你将系统自动达到和维持在这个状态.
- Kubernetes以“一切以服务(Service)为中心,一切围绕服务运转”作为指导思想的创新型产品.它在功能和架构设计上始终遵循着这一指导思想,构建在Kubernetes上的系统不仅可以独立运行在物理机、虚拟机集群或企业私有云上,也可以被托管在公有云上.
- Kubernetes是一个开放的开发平台.不局限于任何一种语言,没有限定任何编程接口,所以不论是用 Java、Go、C++还是 Python 编写的程序,都可以被映射为Kubernetes的 Service,并通过标准的 TCP 通讯协议进行交互.此外,Kubernetes平台对现有的编程语言、编程框架、中间件没有任何侵入性,做到了零侵入,因此现有的系统也很容易改造升级并迁移到Kubernetes平台之上.
- Kubernetes的另一个亮点是自动化.在Kubernetes的解决方案中,一个可以自我扩展、自我诊断,并且容易升级,在收到服务扩容的请求后,Kubernetes会触发调度流程,最终在选定的目标节点上启动相应数据的服务实例副本,这些服务实例副本在启动成功后会自动加入负载均衡器中并生效,整个过程无须额外的人工操作.另外,Kubernetes会定时巡查每个服务的所有实例的可用性,确保服务实例的数量始终保持为预期的数量,当它发现某个实例不可用时,会自动重启该实例或者其他节点上重新调度、运行一个新实例,这样一个复杂的过程无须人工干预即可全部自动完成.
- Kubernetes是一个完备的分布式系统支撑平台.具备完备的集群管理能力,包括多层次的安全防护和准入机制、多租户应用支撑能力、透明的服务注册和服务发现机制、内建的智能负载均衡器、强大的故障发现和自我修复能力、服务滚动升级和在线扩容能力、可扩展的资源自动调度机制,以及多粒度的资源配额管理能力.同时,Kubernetes提供了完善的管理工具,这些涵盖了包括开发、部署测试、运维监控在内的各个环节.因此,Kubernetes是一个全新的基于容器技术的分布式架构解决方案,并且是一个一站式的完备的分布式系统开发和支持平台.
功能组件
- Node:
- kubelet 每个Node节点都会运行一个kubelet进程, 负责向Master汇报自身节点运行情况, 如 Node 节点的注册、终止、定时上报健康状况等,以及接收 Master 发出的命令,创建相应 Pod;
- Container runtime 负责镜像管理以及 Pod 和容器的真正运行(CRI),默认的容器运行时为 Docker;
- Pod 是最基本的操作单元,它与 docker 的容器有略微的不同,因为 Pod 可能包含一个或多个容器(可以是 docker 容器),这些内部的容器是共享网络资源的,即可以通过 localhost 进行相互访问;
- kube-proxy 负责为 Service 提供 cluster 内部的服务发现和负载均衡;
- Master(控制平面):
- Replication Controller - 管理每个 Pod 设置一个期望的副本数,当实际副本数与期望不符时,就动态的进行数量调整,以达到期望值.期望数值可以由我们手动更新,或自动扩容代理来完成.
- Service Controller - K8S 引入了 Service 的概念,将多个相同的 Pod 包装成一个完整的 service 对外提供服务,至于获取到这些相同的 Pod,每个 Pod 启动时都会设置 labels 属性,在 Service 中我们通过选择器 Selector,选择具有相同 Name 标签属性的 Pod,作为整体服务,并将服务信息通过 Apiserver 存入 etcd 中,该工作由 Service Controller 来完成.同时,每个节点上会启动一个 kube-proxy 进程,由它来负责服务地址到 Pod 地址的代理以及负载均衡等工作.
- API Server - 管控中心, 作为K8s整体的管理大脑, 提供了资源操作的唯一入口,并提供认证、授权、访问控制、API 注册和发现等机制;
- Scheduler - 调度中心, 通过执行一系列列复杂的算法最终为每个 Pod 计算出一个最佳的目标 Node, 负责资源的调度,按照预定的调度策略将 Pod 调度到相应的机器上;
- Controller - 控制中心, 作为一个守护进程,每个 Controller 都是一个控制循环,通过 apiserver 监视集群的共享状态,并尝试将实际状态与期望不符的进行改变.
- etcd - 存储中心, 保存了整个集群的状态;
模型组件
- Pod - 可以在主机上运行的容器的集合. 单个Pod是下面工作负载里面的对象.
- 工作负载 - 为 pod 设置部署规则的对象.Kubernetes 基于这些规则执行部署,并根据应用程序的当前状态来更新工作负载. 工作负载让你可以定义应用程序调度、扩展和升级的规则.
- Deployment - 最适合用于无状态应用程序(即不需要维护工作负载的状态).由 Deployment 类型工作负载管理的 Pod 是独立且一次性的.如果 pod 中断了,Kubernetes 会删除该 pod 然后重新创建它.一个示例应用程序是 Nginx Web 服务器. 一个Deployment常常会部署一组Pod;
- StatefulSet - 与 Deployment 相比,StatefulSet 最适合在需要维护身份和存储数据的应用程序中使用.适用的应用程序类似于 Zookeeper(一个需要数据库进行存储的应用程序)
- DaemonSet - Daemonset 确保集群中的每个节点都运行 pod 的副本.如果你需要收集日志或监控节点性能,这种类似 daemon 的工作负载效果是最好的.
- Job - 启动一个或多个 Pod 并确保指定数量的 Pod 能成功终止.Job 最好用于运行有限任务至完成状态,而不是管理正在进行的应用程序的所需状态.
- CronJob - 与 Job 类似.但是,CronJob 会基于 cron 的计划运行到完成状态.
Rancher
从容器编排调度的角度:kubernetes,swam等这些都是容器编排调度框架,包括了引擎和工具,但rancher来了个引擎整合,让你既能任意挑选要部署的引擎,例如创建k8s集群,还能用k8s自己的工具进行管理.那么rancher就成了编排者的编排者了!
接下来我们通过Rancher的操作界面, 来继续了解K8s相关的信息.
- 集群管理 - 左上角说到Rancher是作为K8s的编排者, 管理多集群, 可以切换不同环境/租户.
- 右面有整体的容量使用信息, 当前服务的事件, 健康检查等
Istio
我们已经有了Kubernets, 为什么还需要Istio, 他又是什么. 我们先来对比一下K8s原生和基于sidecar的服务网格. 可以看到Service Mesh比起原生来说多了一个控制面(Control Plane), 同时proxy控制的力度也从node级别细致到Pod级别.
Istio基于Kubernetes能力进行拓展, 各有侧重:
Istio - 服务治理
- 流量管理 - 动态路由, 限流熔断, 故障注入
- 策略控制 - 访问控制
- 可观测性 - 调用链追踪
- 安全 - 验证, 鉴权
Kubernetes - 部署运维
- 负载均衡, 服务发现
- 扩缩容
- 运维, 部署
总结
从最初的物理机时代演进到虚拟化技术, 解决的是物理机性能的利用率和故障隔离, 通过虚拟化来进一步榨取物理机的性能. 容器的出现是更是将虚拟化技术推向高潮, 采用一种更加轻量级的方式来解决环境和配置统一的问题. 依托于互联网时代的微服务生态, 当我们需要大规模去管理容器的时候, Kubernetes横空出世, 让开发运维同学可以更加便捷的管理和维护大规模集群和部署, 能够轻松的动态进行扩缩容, 大规模的缩减成本. 同时依托于Kubernetes的高拓展能力下, 可以高效便捷的添加各种插件来不断丰富我们的集群能力.
(高清原图地址: https://landscape.cncf.io/images/landscape.png)