Kubernetes-秘籍第二版-全-
Kubernetes 秘籍第二版(全)
原文:
annas-archive.org/md5/54ba67c9642c693082c9f497ccfe7f39译者:飞龙
前言
随着近年来微服务架构的流行,单体应用被重构为多个微服务。容器简化了由微服务构建的应用的部署。容器管理、自动化和编排已成为关键问题。Kubernetes 正是为了解决这些问题而诞生的。
本书是一本实用指南,提供了逐步的技巧和示例,帮助你在私有云和公有云中构建和运行自己的 Kubernetes 集群。跟随本书的内容,你将理解如何在 Kubernetes 中部署和管理你的应用和服务。你还将深入理解如何扩展和更新实时容器,以及如何在 Kubernetes 中进行端口转发和网络路由。你将通过本书的动手示例学习如何构建一个强大的高可用集群。最后,你将通过集成 Jenkins、Docker 注册表和 Kubernetes,构建一个持续交付管道。
本书适用对象
如果你已经使用 Docker 容器一段时间,并且想要以现代的方式管理你的容器,那么本书是你的最佳选择。本书适合那些已经理解 Docker 和容器技术,并希望进一步探索以寻找更好的方式来编排、管理和部署容器的人。本书非常适合那些希望超越单一容器并与容器集群一起工作的读者,学习如何构建自己的 Kubernetes,并使其与持续交付管道无缝对接。
本书涵盖内容
第一章,构建你自己的 Kubernetes 集群,讲解了如何使用各种部署工具构建自己的 Kubernetes 集群,并在其上运行你的第一个容器。
第二章,深入理解 Kubernetes 概念,涵盖了我们需要了解的 Kubernetes 的基础和高级概念。接下来,你将学习如何通过编写和应用配置文件,将这些概念结合起来创建 Kubernetes 对象。
第三章,玩转容器,解释了如何扩展容器的规模,并执行滚动更新而不影响应用的可用性。此外,你还将学习如何部署容器来应对不同的应用负载。它还将带你了解配置文件的最佳实践。
第四章,构建高可用集群,提供了如何构建高可用的 Kubernetes master 和 etcd 的相关信息。这将防止 Kubernetes 组件成为单点故障。
第五章,构建持续交付管道,讨论了如何将 Kubernetes 集成到现有的持续交付管道中,使用 Jenkins 和私有 Docker 注册表。
第六章,在 AWS 上构建 Kubernetes,带你了解 AWS 基础知识。你将学习如何在几分钟内在 AWS 上构建一个 Kubernetes 集群。
第七章,在 GCP 上构建 Kubernetes,带你进入 Google Cloud Platform 的世界。你将学习 GCP 的基础知识,并了解如何只需几次点击就能启动一个托管的、生产就绪的 Kubernetes 集群。
第八章,高级集群管理,讨论了 Kubernetes 中的重要资源管理。本章还讲解了其他重要的集群管理内容,如 Kubernetes 仪表板、身份验证和授权。
第九章,日志记录与监控,解释了如何使用 Elasticsearch、Logstash 和 Kibana(ELK)在 Kubernetes 中收集系统和应用程序日志。你还将学习如何利用 Heapster、InfluxDB 和 Grafana 来监控你的 Kubernetes 集群。
为了充分利用本书
在整本书中,我们使用至少三台服务器,运行基于 Linux 的操作系统,来构建 Kubernetes 中的所有组件。在本书开始时,你可以使用一台机器,无论是 Linux 还是 Windows,来了解概念和基础部署。从可扩展性的角度来看,我们建议你从三台服务器开始,以便独立扩展组件并将集群推向生产级别。
下载示例代码文件
你可以从www.packtpub.com的账户中下载本书的示例代码文件。如果你从其他地方购买了本书,可以访问www.packtpub.com/support,注册后直接将文件通过邮件发送给你。
你可以通过以下步骤下载代码文件:
-
登录或注册于www.packtpub.com。
-
选择支持标签。
-
点击代码下载与勘误。
-
在搜索框中输入书名,并按照屏幕上的指示操作。
下载文件后,请确保使用以下最新版本的工具解压或提取文件夹:
-
WinRAR/7-Zip for Windows
-
Zipeg/iZip/UnRarX for Mac
-
7-Zip/PeaZip for Linux
本书的代码包也托管在 GitHub 上,地址是github.com/PacktPublishing/Kubernetes-Cookbook-Second-Edition。如果代码有更新,将会更新至现有的 GitHub 仓库。
我们还从丰富的书籍和视频目录中提供其他代码包,访问github.com/PacktPublishing/。快来看看吧!
下载彩色图片
我们还提供了一份包含本书中使用的截图/图示彩色图片的 PDF 文件。你可以在这里下载:www.packtpub.com/sites/default/files/downloads/KubernetesCookbookSecondEdition_ColorImages.pdf。
使用的约定
本书中使用了若干文本约定。
CodeInText:表示文本中的代码词、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 账户。这里有一个例子:“准备以下 YAML 文件,这是一个简单的 Deployment,启动两个nginx容器。”
代码块如下设置:
# cat 3-1-1_deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
当我们希望引起你对代码块中特定部分的注意时,相关行或项将以粗体显示:
Annotations: deployment.kubernetes.io/revision=1
Selector: env=test,project=My-Happy-Web,role=frontend
Replicas: 5 desired | 5 updated | 5 total | 5 available | 0 unavailable
StrategyType: RollingUpdate
任何命令行输入或输出将按如下方式编写:
//install kubectl command by "kubernetes-cli" package
$ brew install kubernetes-cli
粗体:表示新术语、重要词汇,或者你在屏幕上看到的词汇。例如,菜单或对话框中的词汇会以这种方式出现在文本中。这里有一个例子:“安装很简单,所以我们可以选择默认选项并点击‘下一步’。”
警告或重要说明将以这种方式显示。
提示和技巧将以这种方式显示。
各节
在本书中,你会发现几个经常出现的标题(准备工作、如何做...、如何运作...、还有更多...,以及另见)。
为了提供清晰的完成食谱的指导,请按照以下方式使用这些部分:
准备工作
本节告诉你在食谱中可以期待什么,并描述了如何设置食谱所需的任何软件或初步设置。
如何做...
本节包含执行食谱所需的步骤。
如何运作...
本节通常包含对上一节内容的详细解释。
还有更多...
本节包含关于食谱的附加信息,帮助你对食谱有更深入的了解。
另见
本节提供了指向其他有用信息的链接,这些信息对食谱有帮助。
联系我们
我们始终欢迎读者的反馈。
一般反馈:发送电子邮件至feedback@packtpub.com,并在邮件主题中提及书名。如果你对本书的任何内容有疑问,请通过questions@packtpub.com与我们联系。
勘误:尽管我们已尽最大努力确保内容的准确性,但错误难免发生。如果你在本书中发现错误,请报告给我们。请访问www.packtpub.com/submit-errata,选择你的书籍,点击“勘误提交表单”链接并输入详细信息。
盗版:如果你在互联网上发现任何我们作品的非法复制品,我们将感激你提供该位置地址或网站名称。请通过copyright@packtpub.com与我们联系,并附上材料的链接。
如果你有兴趣成为作者:如果你在某个领域有专业知识,并且有兴趣写书或为书籍做贡献,请访问 authors.packtpub.com。
评论
请留下评论。阅读并使用本书后,为什么不在你购买书籍的网站上留下评论呢?潜在读者可以看到并参考你的公正意见来做出购买决策,我们在 Packt 也可以了解你对我们产品的看法,而我们的作者则能看到你对他们书籍的反馈。谢谢!
欲了解更多关于 Packt 的信息,请访问 packtpub.com。
第一章:构建你自己的 Kubernetes 集群
在本章中,我们将介绍以下方案:
-
探索 Kubernetes 架构
-
通过 minikube 在 macOS 上设置 Kubernetes 集群
-
通过 minikube 在 Windows 上设置 Kubernetes 集群
-
通过 kubeadm 在 Linux 上设置 Kubernetes 集群
-
通过 Ansible (kubespray) 在 Linux 上设置 Kubernetes 集群
-
在 Kubernetes 中运行你的第一个容器
介绍
欢迎踏上 Kubernetes 之旅!在这一部分中,你将学习如何构建自己的 Kubernetes 集群。在理解每个组件并将它们连接在一起的过程中,你将学习如何在 Kubernetes 上运行第一个容器。拥有一个 Kubernetes 集群将帮助你在接下来的章节中继续学习。
探索 Kubernetes 架构
Kubernetes 是一个开源的容器管理工具。它是基于 Go 语言的 (golang.org),轻量级且可移植的应用程序。你可以在基于 Linux 的操作系统上设置 Kubernetes 集群,以部署、管理和扩展多个主机上的 Docker 容器应用。
准备就绪
Kubernetes 由以下组件组成:
-
Kubernetes 主节点
-
Kubernetes 节点
-
etcd
-
Kubernetes 网络
这些组件通过网络连接,如下图所示:

上述图示可以总结如下:
-
Kubernetes 主节点:它通过 HTTP 或 HTTPS 连接到 etcd 以存储数据
-
Kubernetes 节点:它通过 HTTP 或 HTTPS 连接到 Kubernetes 主节点以获取命令并报告状态
-
Kubernetes 网络:它通过 L2、L3 或覆盖网络连接其容器应用程序
如何做到这一点...
在本节中,我们将解释如何使用 Kubernetes 主节点和节点来实现 Kubernetes 系统的主要功能。
Kubernetes 主节点
Kubernetes 主节点是 Kubernetes 集群的主要组件。它提供多个功能,具体如下:
-
授权与认证
-
RESTful API 入口点
-
将容器部署调度到 Kubernetes 节点
-
扩展和复制控制器
-
读取配置以设置集群
下图展示了主节点守护进程如何协同工作,以实现上述功能:

有多个守护进程组成 Kubernetes 主节点的功能,如 kube-apiserver、kube-scheduler 和 kube-controller-manager。Hypercube,作为封装二进制文件,可以启动所有这些守护进程。
此外,Kubernetes 命令行界面 kubectl 可以控制 Kubernetes 主节点的功能。
API 服务器(kube-apiserver)
API 服务器提供基于 HTTP 或 HTTPS 的 RESTful API,它是 Kubernetes 各组件之间的枢纽,如 kubectl、调度器、复制控制器、etcd 数据存储、在 Kubernetes 节点上运行的 kubelet 和 kube-proxy 等。
调度器(kube-scheduler)
调度器帮助选择容器运行的节点。它是一个简单的算法,定义了调度和将容器绑定到节点的优先级。例如:
-
CPU
-
内存
-
有多少个容器在运行?
控制器管理器(kube-controller-manager)
控制器管理器执行集群操作。例如:
-
管理 Kubernetes 节点
-
创建和更新 Kubernetes 内部信息
-
尝试将当前状态更改为目标状态
命令行接口(kubectl)
安装 Kubernetes 主节点后,你可以使用 Kubernetes 命令行接口kubectl来控制 Kubernetes 集群。例如,kubectl get cs返回各个组件的状态。此外,kubectl get nodes返回 Kubernetes 节点的列表:
//see the Component Statuses
# kubectl get cs
NAME STATUS MESSAGE ERROR
controller-manager Healthy ok nil
scheduler Healthy ok nil
etcd-0 Healthy {"health": "true"} nil
//see the nodes
# kubectl get nodes
NAME LABELS STATUS AGE
kub-node1 kubernetes.io/hostname=kub-node1 Ready 26d
kub-node2 kubernetes.io/hostname=kub-node2 Ready 26d
Kubernetes 节点
Kubernetes 节点是 Kubernetes 集群中的从节点。它由 Kubernetes 主节点控制,用于通过 Docker(docker.com)或 rkt(coreos.com/rkt/docs/latest/)运行容器应用程序。在本书中,我们将使用 Docker 容器运行时作为默认引擎。
节点还是从节点?
“从节点”一词在计算机行业中用于表示集群工作节点;然而,它也与歧视相关。Kubernetes 项目在早期版本中使用 minion,在当前版本中使用节点(node)。
以下图展示了节点中守护进程的角色和任务:

节点也有两个守护进程,分别是 kubelet 和 kube-proxy,以支持其功能。
kubelet
kubelet 是 Kubernetes 节点上的主要进程,它与 Kubernetes 主节点进行通信,处理以下操作:
-
定期访问 API 控制器进行检查并报告
-
执行容器操作
-
运行 HTTP 服务器以提供简单的 API
代理(kube-proxy)
代理处理每个容器的网络代理和负载均衡器。它通过更改 Linux iptables 规则(nat 表)来控制跨容器的 TCP 和 UDP 数据包。
启动 kube-proxy 守护进程后,它配置 iptables 规则;你可以使用iptables -t nat -L或iptables -t nat -S来检查 nat 表规则,如下所示:
//the result will be vary and dynamically changed by kube-proxy
# sudo iptables -t nat -S
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N DOCKER
-N FLANNEL
-N KUBE-NODEPORT-CONTAINER
-N KUBE-NODEPORT-HOST
-N KUBE-PORTALS-CONTAINER
-N KUBE-PORTALS-HOST
-A PREROUTING -m comment --comment "handle ClusterIPs; NOTE: this must be before the NodePort rules" -j KUBE-PORTALS-CONTAINER
-A PREROUTING -m addrtype --dst-type LOCAL -m comment --comment "handle service NodePorts; NOTE: this must be the last rule in the chain" -j KUBE-NODEPORT-CONTAINER
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT -m comment --comment "handle ClusterIPs; NOTE: this must be before the NodePort rules" -j KUBE-PORTALS-HOST
-A OUTPUT -m addrtype --dst-type LOCAL -m comment --comment "handle service NodePorts; NOTE: this must be the last rule in the chain" -j KUBE-NODEPORT-HOST
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 192.168.90.0/24 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s 192.168.0.0/16 -j FLANNEL
-A FLANNEL -d 192.168.0.0/16 -j ACCEPT
-A FLANNEL ! -d 224.0.0.0/4 -j MASQUERADE
它是如何工作的……
还有两个组件补充 Kubernetes 节点的功能,数据存储 etcd 和容器间网络。你可以在以下小节中了解它们如何支持 Kubernetes 系统。
etcd
etcd (coreos.com/etcd/)是一个分布式键值数据存储。可以通过 RESTful API 进行访问,从而在网络上执行 CRUD 操作。Kubernetes 使用 etcd 作为主要的数据存储。
你可以使用curl命令在 etcd(/registry)中查看 Kubernetes 的配置和状态,如下所示:
//example: etcd server is localhost and default port is 4001
# curl -L http://127.0.0.1:4001/v2/keys/registry
{"action":"get","node":{"key":"/registry","dir":true,"nodes":[{"key":"/registry/namespaces","dir":true,"modifiedIndex":6,"createdIndex":6},{"key":"/registry/pods","dir":true,"modifiedIndex":187,"createdIndex":187},{"key":"/registry/clusterroles","dir":true,"modifiedIndex":196,"createdIndex":196},{"key":"/registry/replicasets","dir":true,"modifiedIndex":178,"createdIndex":178},{"key":"/registry/limitranges","dir":true,"modifiedIndex":202,"createdIndex":202},{"key":"/registry/storageclasses","dir":true,"modifiedIndex":215,"createdIndex":215},{"key":"/registry/apiregistration.k8s.io","dir":true,"modifiedIndex":7,"createdIndex":7},{"key":"/registry/serviceaccounts","dir":true,"modifiedIndex":70,"createdIndex":70},{"key":"/registry/secrets","dir":true,"modifiedIndex":71,"createdIndex":71},{"key":"/registry/deployments","dir":true,"modifiedIndex":177,"createdIndex":177},{"key":"/registry/services","dir":true,"modifiedIndex":13,"createdIndex":13},{"key":"/registry/configmaps","dir":true,"modifiedIndex":52,"createdIndex":52},{"key":"/registry/ranges","dir":true,"modifiedIndex":4,"createdIndex":4},{"key":"/registry/minions","dir":true,"modifiedIndex":58,"createdIndex":58},{"key":"/registry/clusterrolebindings","dir":true,"modifiedIndex":171,"createdIndex":171}],"modifiedIndex":4,"createdIndex":4}}
Kubernetes 网络
容器之间的网络通信是最困难的部分。因为 Kubernetes 管理多个运行着多个容器的节点(主机),这些不同节点上的容器可能需要彼此通信。
如果容器的网络通信仅限于单个节点内,你可以使用 Docker 网络或 Docker Compose 来发现对等体。然而,随着节点的增加,Kubernetes 使用叠加网络或容器网络接口(CNI)来实现多个容器之间的通信。
另请参见
本食谱描述了 Kubernetes 的基本架构和方法论以及相关组件。理解 Kubernetes 并不容易,但逐步学习如何设置、配置和管理 Kubernetes 真的很有趣。
使用 minikube 在 macOS 上设置 Kubernetes 集群
Kubernetes 由多个开源组件组成。这些组件由不同的方开发,使得很难找到并下载所有相关的包,并从零开始安装、配置并使其工作。
幸运的是,已经开发出一些不同的解决方案和工具,可以轻松设置 Kubernetes 集群。因此,强烈建议你使用这些工具来设置你环境中的 Kubernetes。
以下工具按不同类型的解决方案进行分类,帮助你构建自己的 Kubernetes:
-
包括以下自我管理解决方案:
-
minikube
-
kubeadm
-
kubespray
-
kops
-
-
包括以下企业解决方案:
-
OpenShift (
www.openshift.com) -
Tectonic (
coreos.com/tectonic/)
-
-
包括以下云托管解决方案:
-
Google Kubernetes 引擎 (
cloud.google.com/kubernetes-engine/) -
亚马逊弹性容器服务 Kubernetes(Amazon EKS,
aws.amazon.com/eks/) -
Azure 容器服务(AKS,
azure.microsoft.com/en-us/services/container-service/)
-
如果我们只是想快速构建一个开发环境或进行概念验证,自我管理解决方案是合适的。
通过使用 minikube (github.com/kubernetes/minikube) 和 kubeadm (kubernetes.io/docs/admin/kubeadm/),我们可以轻松地在本地机器上构建所需的环境;然而,如果我们想要构建生产环境,这就不太实际了。
通过使用 kubespray (github.com/kubernetes-incubator/kubespray) 和 kops (github.com/kubernetes/kops),我们也可以快速从零开始构建生产级环境。
如果我们想创建一个生产环境,企业解决方案或云托管解决方案是最简单的起点。特别是Google Kubernetes Engine(GKE),这是 Google 多年来一直使用的解决方案,提供了全面的管理,意味着用户不需要过多关注安装和设置。此外,Amazon EKS 是 AWS 在 2017 年 re:Invent 上推出的一项新服务,由 AWS 上的 Kubernetes 服务进行管理。
Kubernetes 还可以通过自定义解决方案在不同的云平台和本地虚拟机上运行。为了入门,本章将展示如何在 macOS 台式机上使用 minikube 构建 Kubernetes。
准备工作
minikube 在 macOS 上的 Linux 虚拟机上运行 Kubernetes。它依赖于虚拟化程序(虚拟化技术),如 VirtualBox (www.virtualbox.org)、VMWare Fusion (www.vmware.com/products/fusion.html) 或 hyperkit (github.com/moby/hyperkit)。此外,我们还需要 Kubernetes 的命令行界面(CLI)kubectl,用于通过虚拟化程序连接并控制 Kubernetes。
使用 minikube,你可以在 macOS 上运行整个 Kubernetes 堆栈,包括 Kubernetes 主节点、节点和 CLI。建议 macOS 有足够的内存来运行 Kubernetes。默认情况下,minikube 使用 VirtualBox 作为虚拟化程序。
然而,在本章中,我们将演示如何使用 hyperkit,它是最轻量级的解决方案。由于 Linux 虚拟机消耗 2 GB 的内存,建议至少使用 4 GB 内存。请注意,hyperkit 是建立在 macOS 上的虚拟化框架之上的 (developer.apple.com/documentation/hypervisor),因此需要 macOS 10.10 Yosemite 或更高版本。
下图显示了 kubectl、虚拟化程序、minikube 和 macOS 之间的关系:

如何操作...
macOS 没有官方的包管理工具,如 Linux 上的 yum 和 apt-get,但 macOS 有一些有用的工具可供使用。Homebrew (brew.sh) 是最受欢迎的包管理工具,管理着许多开源工具,包括 minikube。
为了在 macOS 上安装 Homebrew,请执行以下步骤:
- 打开终端,然后输入以下命令:
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
- 安装完成后,你可以输入
/usr/local/bin/brew help来查看可用的命令选项。
如果你刚在 macOS 上安装或升级了 Xcode,Homebrew 安装可能会停止。在这种情况下,请打开 Xcode 接受许可协议,或者事先输入 sudo xcodebuild -license。
- 接下来,安装 minikube 的
hyperkit driver。截至撰写本文时(2018 年 2 月),HomeBrew 不支持 hyperkit,因此输入以下命令进行安装:
$ curl -LO https://storage.googleapis.com/minikube/releases/latest/docker-machine-driver-hyperkit \
&& chmod +x docker-machine-driver-hyperkit \
&& sudo mv docker-machine-driver-hyperkit /usr/local/bin/ \
&& sudo chown root:wheel /usr/local/bin/docker-machine-driver-hyperkit \
&& sudo chmod u+s /usr/local/bin/docker-machine-driver-hyperkit
- 接下来,让我们安装 Kubernetes CLI。使用 Homebrew 执行以下命令在您的 macOS 上安装
kubectl命令:
//install kubectl command by "kubernetes-cli" package
$ brew install kubernetes-cli
最后,您可以安装 minikube。它不是由 Homebrew 管理的;但是,Homebrew 有一个扩展工具 homebrew-cask (github.com/caskroom/homebrew-cask),它支持 minikube。
- 若要通过
homebrew-cask安装 minikube,只需输入以下命令:
//add "cask" option
$ brew cask install minikube
- 如果您从未在您的机器上安装过 Docker for Mac,也需要通过
homebrew-cask来安装它。
//only if you don't have a Docker for Mac
$ brew cask install docker
//start Docker
$ open -a Docker.app
- 现在一切就绪!以下命令将显示是否已在您的 macOS 上安装所需的软件包:
//check installed package by homebrew
$ brew list
kubernetes-cli
//check installed package by homebrew-cask
$ brew cask list
minikube
工作原理...
minikube 适合在 macOS 上设置 Kubernetes,使用以下命令,它会下载并启动一个 Kubernetes 虚拟机实例,然后配置 kubectl 配置文件(~/.kube/config):
//use --vm-driver=hyperkit to specify to use hyperkit
$ /usr/local/bin/minikube start --vm-driver=hyperkit
Starting local Kubernetes v1.10.0 cluster...
Starting VM...
Downloading Minikube ISO
150.53 MB / 150.53 MB [============================================] 100.00% 0s
Getting VM IP address...
Moving files into cluster...
Downloading kubeadm v1.10.0
Downloading kubelet v1.10.0
Finished Downloading kubelet v1.10.0
Finished Downloading kubeadm v1.10.0
Setting up certs...
Connecting to cluster...
Setting up kubeconfig...
Starting cluster components...
Kubectl is now configured to use the cluster.
Loading cached images from config file.
//check whether .kube/config is configured or not
$ cat ~/.kube/config
apiVersion: v1
clusters:
- cluster:
certificate-authority: /Users/saito/.minikube/ca.crt
server: https://192.168.64.26:8443
name: minikube
contexts:
- context:
cluster: minikube
user: minikube
name: minikube
current-context: minikube
kind: Config
preferences: {}
users:
- name: minikube
user:
as-user-extra: {}
client-certificate: /Users/saito/.minikube/client.crt
client-key: /Users/saito/.minikube/client.key
获取所有必要的软件包后,请执行以下步骤:
-
等待几分钟,直到 Kubernetes 集群设置完成。
-
使用
kubectl version检查 Kubernetes 主版本,使用kubectl get cs查看组件状态。 -
另外,使用
kubectl get nodes命令检查 Kubernetes 节点是否已准备好:
//it shows kubectl (Client) is 1.10.1, and Kubernetes master (Server) is 1.10.0
$ /usr/local/bin/kubectl version --short
Client Version: v1.10.1
Server Version: v1.10.0
//get cs will shows Component Status
$ kubectl get cs
NAME STATUS MESSAGE ERROR
controller-manager Healthy ok
scheduler Healthy ok
etcd-0 Healthy {"health": "true"}
//Kubernetes node (minikube) is ready
$ /usr/local/bin/kubectl get nodes
NAME STATUS ROLES AGE VERSION
minikube Ready master 2m v1.10.0
-
现在,您可以开始在机器上使用 Kubernetes。以下部分描述了如何使用
kubectl命令操作 Docker 容器。 -
请注意,在某些情况下,您可能需要维护 Kubernetes 集群,例如启动/停止虚拟机或完全删除它。以下命令用于维护 minikube 环境:
| 命令 | 目的 |
|---|---|
minikube start --vm-driver=hyperkit |
使用 hyperkit 驱动程序启动 Kubernetes 虚拟机 |
minikube stop |
停止 Kubernetes 虚拟机 |
minikube delete |
删除 Kubernetes 虚拟机镜像 |
minikube ssh |
SSH 连接到 Kubernetes 虚拟机 |
minikube ip |
显示 Kubernetes 虚拟机(节点)IP 地址 |
minikube update-context |
如果虚拟机 IP 地址发生更改,检查并更新 ~/.kube/config |
minikube dashboard |
打开浏览器连接到 Kubernetes UI |
例如,minikube 默认启动一个仪表盘(Kubernetes UI)。如果您想访问仪表盘,输入 minikube dashboard,然后它会打开您的默认浏览器并连接到 Kubernetes UI,如下图所示:

另见
本教程描述了如何使用 minikube 在 macOS 上设置 Kubernetes 集群。这是开始使用 Kubernetes 的最简单方法。我们还学习了如何使用 kubectl,这是 Kubernetes 的命令行接口工具,它是控制 Kubernetes 集群的入口!
在 Windows 上使用 minikube 设置 Kubernetes 集群
本质上,Docker 和 Kubernetes 是基于 Linux 操作系统的。虽然使用 Windows 操作系统来探索 Kubernetes 并非理想选择,但许多人仍然将 Windows 操作系统作为桌面或笔记本电脑的操作系统。幸运的是,通过虚拟化技术,有很多方法可以在 Windows 上运行 Linux 操作系统,这使得在 Windows 机器上运行 Kubernetes 集群成为可能。然后,我们可以在本地 Windows 机器上构建开发环境或进行概念验证。
你可以通过在 Windows 上使用任何虚拟化软件从头开始设置 Kubernetes,来运行 Linux 虚拟机,但使用 minikube (github.com/kubernetes/minikube) 是在 Windows 上构建 Kubernetes 集群的最快方式。请注意,这个方法并不适用于生产环境,因为它会在 Windows 上设置一个运行 Kubernetes 的 Linux 虚拟机。
准备工作
在 Windows 上设置 minikube 需要一个虚拟化软件,可能是 VirtualBox (www.virtualbox.org) 或 Hyper-V,因为,minikube 使用 Windows 上的 Linux 虚拟机。这意味着你不能使用 Windows 虚拟机(例如,在 macOS 上通过 Parallels 运行 Windows 虚拟机)。
然而,kubectl,Kubernetes 的命令行工具,支持一个可以通过网络连接到 Kubernetes 的 Windows 本地二进制文件。因此,你可以在 Windows 机器上设置一个便携式 Kubernetes 套件。
下图展示了 kubectl、虚拟化软件、minikube 和 Windows 之间的关系:

Windows 8 Pro 或更高版本需要 Hyper-V。虽然许多用户仍然使用 Windows 7,但我们在这个教程中将使用 VirtualBox 作为 minikube 的虚拟化软件。
如何操作...
首先,需要安装 Windows 版的 VirtualBox:
-
访问 VirtualBox 网站 (
www.virtualbox.org/wiki/Downloads) 下载 Windows 安装程序。 -
安装过程非常简单,所以我们只需选择默认选项并点击“下一步”:

- 接下来,创建一个
Kubernetes文件夹,用于存储 minikube 和 kubectl 可执行文件。我们将在C:盘上创建一个k8s文件夹,如下图所示:

-
这个文件夹必须在命令搜索路径中,因此打开系统属性,然后转到“高级”选项卡。
-
点击“环境变量...”按钮,然后选择“Path”,接着点击“编辑...”按钮,如下图所示:

- 然后,添加
c:\k8s,如下所示:

-
单击“确定”按钮后,注销并重新登录 Windows(或重启)以应用此更改。
-
接下来,下载适用于 Windows 的 minikube。它是一个单独的二进制文件,使用任何 Web 浏览器下载
github.com/kubernetes/minikube/releases/download/v0.26.1/minikube-windows-amd64,然后将其复制到c:\k8s文件夹中,但请将文件名更改为minikube.exe。 -
接下来,下载适用于 Windows 的 kubectl,它可以与 Kubernetes 通信。它也是像 minikube 一样的单一二进制文件。所以,下载
storage.googleapis.com/kubernetes-release/release/v1.10.2/bin/windows/amd64/kubectl.exe,然后也将其复制到c:\k8s文件夹中。 -
最终,你将在
c:\k8s文件夹中看到两个二进制文件,如下图所示:

如果你正在运行防病毒软件,它可能会阻止你运行kubectl.exe和minikube.exe。如果是这种情况,请更新防病毒软件设置,允许运行这两个二进制文件。
它是如何工作的...
让我们开始吧!
- 打开命令提示符并输入
minikube start,如以下截图所示:

- minikube 下载 Linux 虚拟机镜像并在其上设置 Kubernetes;现在如果你打开 VirtualBox,你会看到 minikube 虚拟机已经注册,如下图所示:

-
等待几分钟以完成 Kubernetes 集群的设置。
-
如下图所示,输入
kubectl version以检查 Kubernetes 主版本。 -
使用
kubectl get nodes命令检查 Kubernetes 节点是否已准备好:

-
现在你可以开始在你的机器上使用 Kubernetes 了!再次强调,Kubernetes 运行在 Linux 虚拟机上,如下图所示。
-
使用
minikube ssh可以访问运行 Kubernetes 的 Linux 虚拟机:

因此,任何基于 Linux 的 Docker 镜像都能够在你的 Windows 机器上运行。
- 输入
minikube ip来验证 Linux 虚拟机使用的 IP 地址,同时输入minikube dashboard,以打开默认的 Web 浏览器并导航到 Kubernetes UI,如下图所示:

- 如果你不再需要使用 Kubernetes,输入
minikube stop,或者打开 VirtualBox 停止 Linux 虚拟机并释放资源,如下图所示:

另见
本教程描述了如何使用 minikube 在 Windows 操作系统上设置 Kubernetes 集群。这是开始使用 Kubernetes 的最简单方式。它还介绍了 kubectl,这是 Kubernetes 的命令行工具,是用来控制 Kubernetes 的入口工具。
通过 kubeadm 在 Linux 上设置 Kubernetes 集群
在本教程中,我们将展示如何在 Linux 服务器上使用 kubeadm 创建一个 Kubernetes 集群 (github.com/kubernetes/kubeadm)。Kubeadm 是一个命令行工具,用于简化创建和管理 Kubernetes 集群的过程。Kubeadm 利用 Docker 的快速部署功能,将 Kubernetes 主节点和 etcd 服务器的系统服务作为容器运行。当通过 kubeadm 命令触发时,容器服务将直接联系 Kubernetes 节点上的 kubelet;kubeadm 还会检查每个组件是否正常。通过 kubeadm 的设置步骤,您可以避免在从头开始构建时需要执行一堆安装和配置命令。
准备就绪
我们将提供两种类型操作系统的说明:
-
Ubuntu Xenial 16.04 (LTS)
-
CentOS 7.4
在继续之前,请确保操作系统版本匹配。此外,软件依赖和网络设置也应在继续到下一步之前进行验证。检查以下项目以准备环境:
- 每个节点都有唯一的 MAC 地址和产品 UUID:某些插件使用 MAC 地址或产品 UUID 作为唯一机器 ID 来识别节点(例如,
kube-dns)。如果它们在集群中重复,kubeadm 在启动插件时可能无法正常工作:
// check MAC address of your NIC $ ifconfig -a
// check the product UUID on your host
$ sudo cat /sys/class/dmi/id/product_uuid
-
每个节点都有不同的主机名:如果主机名重复,Kubernetes 系统可能会将来自多个节点的日志或状态收集到同一个节点。
-
已安装 Docker:如前所述,Kubernetes 主节点将作为容器运行其守护进程,并且集群中的每个节点都应安装 Docker。有关如何安装 Docker,可以按照官方网站上的步骤进行操作:(Ubuntu:
docs.docker.com/engine/installation/linux/docker-ce/ubuntu/,CentOS:docs.docker.com/engine/installation/linux/docker-ce/centos/)。在我们的机器上已经安装了 Docker CE 17.06;然而,只有 Docker 版本 1.11.2 至 1.13.1,以及 17.03.x 与 Kubernetes 版本 1.10 验证过。 -
网络端口可用:Kubernetes 系统服务需要网络端口进行通信。根据节点的角色,下面表格中的端口应当被占用:
| 节点角色 | 端口 | 系统服务 |
|---|---|---|
| 主节点 | 6443 |
Kubernetes API 服务器 |
10248/10250/10255 |
kubelet 本地 healthz 端点/Kubelet API/Heapster(只读) | |
10251 |
kube-scheduler | |
10252 |
kube-controller-manager | |
10249/10256 |
kube-proxy | |
2379/2380 |
etcd 客户端/etcd 服务器通信 | |
| 节点 | 10250/10255 |
Kubelet API/Heapster(只读) |
30000~32767 |
用于将容器服务暴露给外部世界的端口范围 |
- Linux 命令
netstat可以帮助检查端口是否在使用中:
// list every listening port
$ sudo netstat -tulpn | grep LISTEN
- 安装网络工具包。
ethtool和ebtables是 kubeadm 所需的两个工具。它们可以通过apt-get或yum包管理工具下载并安装。
如何操作...
本菜谱将分别介绍两种 Linux 操作系统(Ubuntu 和 CentOS)的安装过程,因为它们的设置方式不同。
包安装
首先获取 Kubernetes 包!需要在包管理系统的源列表中设置下载仓库。然后,我们可以通过命令行轻松安装它们。
Ubuntu
在 Ubuntu 中安装 Kubernetes 包,请执行以下步骤:
- 一些仓库的 URL 使用 HTTPS。必须安装
apt-transport-https包才能访问 HTTPS 端点:
$ sudo apt-get update && sudo apt-get install -y apt-transport-https
- 下载用于访问 Google Cloud 上包的公钥,并按以下方式添加:
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
OK
- 接下来,添加一个新的源列表用于 Kubernetes 包:
$ sudo bash -c 'echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" > /etc/apt/sources.list.d/kubernetes.list'
- 最后,最好安装 Kubernetes 包:
// on Kubernetes master
$ sudo apt-get update && sudo apt-get install -y kubelet kubeadm kubectl
// on Kubernetes node
$ sudo apt-get update && sudo apt-get install -y kubelet
CentOS
在 CentOS 中安装 Kubernetes 包,请执行以下步骤:
- 与 Ubuntu 一样,需要添加新的仓库信息:
$ sudo vim /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg
https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
- 现在,我们已经准备好通过
yum命令从 Kubernetes 源库拉取包:
// on Kubernetes master
$ sudo yum install -y kubelet kubeadm kubectl
// on Kubernetes node
$ sudo yum install -y kubelet
- 无论是什么操作系统,检查你获取的包的版本!
// take it easy! server connection failed since there is not server running
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"10", GitVersion:"v1.10.2", GitCommit:"81753b10df112992bf51bbc2c2f85208aad78335", GitTreeState:"clean", BuildDate:"2018-04-27T09:22:21Z", GoVersion:"go1.9.3", Compiler:"gc", Platform:"linux/amd64"}
The connection to the server 192.168.122.101:6443 was refused - did you specify the right host or port?
系统配置前提条件
在通过 kubeadm 启动整个系统之前,请确保 Docker 已在你的机器上运行。除此之外,为了避免在执行 kubeadm 时发生严重错误,我们将展示系统和 kubelet 上必要的服务配置。作为主节点,请在 Kubernetes 节点上设置以下配置,以确保 kubelet 能与 kubeadm 正常工作。
CentOS 系统设置
在 CentOS 中还有其他附加设置,以确保 Kubernetes 正常工作。请注意,即使我们没有使用 kubeadm 来管理 Kubernetes 集群,以下设置仍应在运行 kubelet 时考虑:
- 禁用 SELinux,因为 kubelet 完全不支持 SELinux:
// check the state of SELinux, if it has already been disabled, bypass below commands
$ sestatus
我们可以通过以下命令 disable SELinux,或者通过 修改配置文件 来禁用 SELinux:
// disable SELinux through command
$ sudo setenforce 0
// or modify the configuration file $ sudo sed –I 's/ SELINUX=enforcing/SELINUX=disabled/g' /etc/sysconfig/selinux
然后我们需要 reboot 机器:
// reboot is required
$ sudo reboot
- 启用 iptables 的使用。为了防止发生一些路由错误,请添加运行时参数:
// enable the parameters by setting them to 1
$ sudo bash -c 'echo "net.bridge.bridge-nf-call-ip6tables = 1" > /etc/sysctl.d/k8s.conf'
$ sudo bash -c 'echo "net.bridge.bridge-nf-call-iptables = 1" >> /etc/sysctl.d/k8s.conf'
// reload the configuration
$ sudo sysctl --system
启动服务
现在我们可以启动服务了。首先启用并启动 Kubernetes 主节点上的 kubelet:
$ sudo systemctl enable kubelet && sudo systemctl start kubelet
在检查 kubelet 状态时,你可能会担心看到状态显示为激活(auto-restart);并且你可能会更为沮丧,看到通过 journalctl 命令查看的详细日志,如下所示:
error: unable to load client CA file /etc/kubernetes/pki/ca.crt: open /etc/kubernetes/pki/ca.crt: no such file or directory
不用担心,kubeadm 会负责创建证书颁发机构文件。该文件在服务配置文件 /etc/systemd/system/kubelet.service.d/10-kubeadm.conf 中通过参数 KUBELET_AUTHZ_ARGS 来定义。如果没有这个文件,kubelet 服务将无法正常工作,因此可以尝试自行重启该守护进程。
继续通过 kubeadm 启动所有主节点守护进程。值得注意的是,使用 kubeadm 需要 root 权限来获得服务级别的特权。对于任何 sudo 用户,每个 kubeadm 命令都需要在 sudo 命令后运行:
$ sudo kubeadm init
在执行 kubeadm init 命令时,如果出现预检错误?使用以下命令来禁用交换分区,如描述所示。
$ sudo kubeadm init --ignore-preflight-errors=Swap
你将会在屏幕上看到 Your Kubernetes master has initialized successfully! 这句话。恭喜!你几乎完成了!只需按照问候信息下方的用户环境设置说明继续操作:
$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config
上述命令确保每个 Kubernetes 指令都由你的账户执行,并具有正确的凭据,并连接到正确的服务器门户:
// Your kubectl command works great now
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"10", GitVersion:"v1.10.2", GitCommit:"81753b10df112992bf51bbc2c2f85208aad78335", GitTreeState:"clean", BuildDate:"2018-04-27T09:22:21Z", GoVersion:"go1.9.3", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"10", GitVersion:"v1.10.2", GitCommit:"81753b10df112992bf51bbc2c2f85208aad78335", GitTreeState:"clean", BuildDate:"2018-04-27T09:10:24Z", GoVersion:"go1.9.3", Compiler:"gc", Platform:"linux/amd64"}
更重要的是,kubelet 现在进入了健康状态:
// check the status of kubelet
$ sudo systemctl status kubelet
...
Active: active (running) Mon 2018-04-30 18:46:58 EDT; 2min 43s ago
...
容器的网络配置
在集群的主节点准备好处理任务并且服务正在运行之后,为了使容器通过网络互相访问,我们需要为容器间通信设置网络。在使用 kubeadm 构建 Kubernetes 集群时,这一步尤为重要,因为主进程都是以容器形式运行的。kubeadm 支持 CNI(github.com/containernetworking/cni)。我们将通过 Kubernetes 网络附加组件来连接 CNI。
有许多第三方 CNI 解决方案提供安全可靠的容器网络环境。Calico(www.projectcalico.org)是一个提供稳定容器网络的 CNI。Calico 轻量简洁,但仍然很好地遵循 CNI 标准并与 Kubernetes 集成:
$ kubectl apply -f https://docs.projectcalico.org/v2.6/getting-started/kubernetes/installation/hosted/kubeadm/1.6/calico.yaml
在这里,无论你的主机操作系统是什么,kubectl 命令都可以执行任何子命令来利用资源和管理系统。我们使用 kubectl 将 Calico 的配置应用到我们的新 Kubernetes 集群中。
更高级的网络管理和 Kubernetes 附加组件将会在第七章中讨论,在 GCP 上构建 Kubernetes。
获取节点参与
让我们登录到你的 Kubernetes 节点,加入由 kubeadm 控制的群组:
- 首先,启用并启动服务
kubelet。每台 Kubernetes 机器上都应该运行kubelet:
$ sudo systemctl enable kubelet && sudo systemctl start kubelet
- 之后,使用
kubeadm加入命令,输入标志 token 和主节点的 IP 地址,通知主节点这是一个安全且授权的节点。你可以通过kubeadm命令在主节点上获取 token:
// on master node, list the token you have in the cluster
$ sudo kubeadm token list
TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS
da3a90.9a119695a933a867 6h 2018-05-01T18:47:10-04:00 authentication,signing The default bootstrap token generated by 'kubeadm init'. system:bootstrappers:kubeadm:default-node-token
- 在前面的输出中,如果
kubeadm init成功,默认令牌将被生成。复制令牌并粘贴到节点上,然后组成以下命令:
// The master IP is 192.168.122.101, token is da3a90.9a119695a933a867, 6443 is the port of api server.
$ sudo kubeadm join --token da3a90.9a119695a933a867 192.168.122.101:6443 --discovery-token-unsafe-skip-ca-verification
如果你调用 kubeadm token list 来列出令牌,却发现它们都过期了怎么办?你可以通过以下命令手动创建一个新的令牌:kubeadm token create。
- 请确保主节点的防火墙没有阻止任何到
6443端口的流量,该端口用于 API 服务器通信。一旦你看到屏幕上显示Successfully established connection,就可以检查主节点是否已经接收到新成员。
// fire kubectl subcommand on master
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
ubuntu01 Ready master 11h v1.10.2
ubuntu02 Ready <none> 26s v1.10.2
做得好!无论你的操作系统是 Ubuntu 还是 CentOS,kubeadm 都已安装并且 kubelet 正在运行。你可以轻松地按照前面的步骤来构建你的 Kubernetes 集群。
你可能会想了解在加入集群时使用的标志 discovery-token-unsafe-skip-ca-verification。还记得 kubelet 日志中提到找不到证书文件吗?就是因为这样,因为我们的 Kubernetes 节点是全新的,之前从未与主节点连接过,所以没有可供验证的证书文件。但现在,因为节点已经与主节点完成了握手,文件存在了。我们可以这样加入(在某些需要重新加入同一集群的情况下):
kubeadm join --token $TOKEN $MASTER_IPADDR:6443 --discovery-token-ca-cert-hash sha256:$HASH
哈希值可以通过 openssl 命令获得:
// rejoining the same cluster
$ HASH=$(openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //')
$ sudo kubeadm join --token da3a90.9a119695a933a867 192.168.122.101:6443 --discovery-token-ca-cert-hash sha256:$HASH
它是如何工作的……
当 kubeadm init 设置主节点时,共有六个阶段:
-
为服务生成证书文件和密钥:证书文件和密钥用于跨节点通信中的安全管理。它们位于
/etc/kubernetes/pki目录下。以 kubelet 为例,如果没有通过身份验证,它无法访问 Kubernetes API 服务器。 -
编写 kubeconfig 文件:
kubeconfig文件定义了 kubectl 操作的权限、身份验证和配置。在这种情况下,Kubernetes 控制器管理器和调度器有相关的kubeconfig文件,以处理任何 API 请求。 -
创建服务守护进程 YAML 文件:在 kubeadm 控制下的服务守护进程就像在主节点上运行的计算组件一样。与设置磁盘上的部署配置类似,kubelet 会确保每个守护进程保持活动状态。
-
等待 kubelet 存活,运行作为 Pod 的守护进程:当 kubelet 存活时,它会启动
/etc/kubernetes/manifests目录下文件中描述的服务 Pod。此外,kubelet 确保这些 Pod 始终保持活动状态,如果 Pod 崩溃,它会自动重启。 -
为集群设置后配置:一些集群配置仍然需要设置,例如配置 基于角色的访问控制 (RBAC) 规则、创建命名空间和标记资源。
-
应用附加组件:DNS 和代理服务可以与 kubeadm 系统一起添加。
当用户输入 kubeadm 并加入 Kubernetes 节点时,kubeadm 将像主节点一样完成前两个阶段。
如果你曾经面对过早期版本 Kubernetes 的复杂安装过程,那么使用 kubeadm 安装 Kubernetes 集群无疑是一种解脱。kubeadm 减少了配置每个守护进程并逐一启动它们的开销。用户仍然可以通过修改熟悉的文件10-kubeadm.conf以及/etc/kubernetes/manifests下的 YAML 文件,来定制 kubelet 和主节点服务。kubeadm 不仅有助于建立集群,还增强了安全性和可用性,节省了你的时间。
另见
我们讨论了如何构建 Kubernetes 集群。如果你准备好在集群上运行你的第一个应用程序,请查看本章的最后一个食谱并运行容器!如果需要更高级的集群管理,你还可以查看本书的第八章,高级集群管理:
- kubeconfig 中的高级设置,详见第八章,高级集群管理
通过 Ansible(kubespray)在 Linux 上设置 Kubernetes 集群
如果你熟悉配置管理工具,如 Puppet、Chef 和 Ansible,kubespray(github.com/kubernetes-incubator/kubespray)是从零开始设置 Kubernetes 集群的最佳选择。它提供了支持大多数 Linux 发行版和公共云(如 AWS 和 GCP)的 Ansible 剧本。
Ansible(www.ansible.com)是一个基于 Python 的 SSH 自动化工具,可以根据配置(称为剧本)将 Linux 配置为所需状态。本食谱描述了如何使用 kubespray 在 Linux 上设置 Kubernetes。
准备工作
截至 2018 年 5 月,kubespray 的最新版本为 2.5.0,支持以下操作系统来安装 Kubernetes:
-
RHEL/CentOS 7
-
Ubuntu 16.04 LTS
根据 kubespray 文档,它还支持 CoreOS 和 Debian 发行版。然而,这些发行版可能需要一些额外的步骤或遇到技术难题。本食谱使用 CentOS 7 和 Ubuntu 16.04 LTS。
此外,你还需要在你的机器上安装 Ansible。Ansible 支持 Python 2.6、2.7 和 3.5 或更高版本。由于大多数 macOS 和 Linux 发行版默认预装了 Python,因此 macOS 和 Linux 可能是安装 Ansible 的最佳选择。要检查你安装了哪个版本的 Python,请打开终端并输入以下命令:
//Use capital V
$ python -V
Python 2.7.5
总体来说,你至少需要三台机器,如下表所示:
| 主机类型 | 推荐操作系统/发行版 |
|---|---|
| Ansible | macOS 或任何具有 Python 2.6、2.7 或 3.5 的 Linux |
| Kubernetes 主节点 | RHEL/CentOS 7 或 Ubuntu 16.04 LTS |
| Kubernetes 节点 | RHEL/CentOS 7 或 Ubuntu 16.04 LTS |
由于存在一些相互通信的网络,因此你至少需要开放一个网络端口(例如,AWS 安全组或 GCP 防火墙规则),如:
-
TCP/22(ssh):Ansible 到 Kubernetes 主节点/节点主机
-
TCP/6443(Kubernetes API 服务器):Kubernetes 节点到主节点
-
协议 4(IP 封装在 IP 中):Kubernetes 主节点和节点之间通过 Calico 通信
在协议 4(IP 封装在 IP 中)中,如果你使用 AWS,设置一个入口规则来指定 aws ec2 authorize-security-group-ingress --group-id <your SG ID> --cidr <network CIDR> --protocol 4。另外,如果你使用 GCP,设置防火墙规则,指定 cloud compute firewall-rules create allow-calico --allow 4 --network <your network name> --source-ranges <network CIDR>。
安装 pip
安装 Ansible 的最简单方法是使用 pip,Python 包管理器。某些较新的 Python 版本已经自带 pip(Python 2.7.9 或更高版本,Python 3.4 或更高版本):
- 要确认是否安装了
pip,可以使用类似 Python 命令的-V:
//use capital V
$ pip -V
pip 9.0.1 from /Library/Python/2.7/site-packages (python 2.7)
- 另一方面,如果你看到以下结果,你需要安装
pip:
//this result shows you don't have pip yet
$ pip -V
-bash: pip: command not found
- 为了安装 pip,下载
get-pip.py并使用以下命令安装:
//download pip install script
$ curl -LO https://bootstrap.pypa.io/get-pip.py
//run get-pip.py by privileged user (sudo)
$ sudo python get-pip.py
Collecting pip
Downloading pip-9.0.1-py2.py3-none-any.whl (1.3MB)
100% |################################| 1.3MB 779kB/s
Collecting wheel
Downloading wheel-0.30.0-py2.py3-none-any.whl (49kB)
100% |################################| 51kB 1.5MB/s
Installing collected packages: pip, wheel
Successfully installed pip-9.0.1 wheel-0.30.0
//now you have pip command
$ pip -V
pip 9.0.1 from /usr/lib/python2.7/site-packages (python 2.7)
安装 Ansible
执行以下步骤来安装 Ansible:
- 一旦你安装了
pip,你可以使用以下命令安装 Ansible:
//ran by privileged user (sudo)
$ sudo pip install ansible
pip 会扫描你的 Python 环境并安装 Ansible 所需的库,因此可能需要几分钟才能完成。
- 一旦你成功通过
pip安装了 Ansible,你可以通过以下命令进行验证,看到如下输出:
$ which ansible
/usr/bin/ansible
$ ansible --version
ansible 2.4.1.0
安装 python-netaddr
接下来,根据 kubespray 的文档(github.com/kubernetes-incubator/kubespray#requirements),它需要 python-netaddr 包。这个包也可以通过 pip 安装,如下所示:
$ sudo pip install netaddr
设置 ssh 公钥认证
还有一件事,如前所述,Ansible 实际上是一个 ssh 自动化工具。如果你通过 ssh 登录主机,你必须拥有适当的凭证(用户名/密码或 ssh 公钥)才能访问目标机器。在这种情况下,目标机器指的是 Kubernetes 主节点和节点。
出于安全原因,特别是在公共云环境中,Kubernetes 只使用 ssh 公钥认证,而不使用 ID/密码认证。
为了遵循最佳实践,让我们将 ssh 公钥从你的 Ansible 机器复制到 Kubernetes 主节点/节点机器:
如果你已经设置了 Ansible 机器与 Kubernetes 候选机器之间的 ssh 公钥认证,你可以跳过此步骤。
- 为了从你的 Ansible 机器创建 ssh 公私钥对,请输入以下命令:
//with –q means, quiet output
$ ssh-keygen -q
-
它会要求你设置一个密码短语。你可以设置或跳过(留空),但你必须记住它。
-
一旦成功创建了密钥对,你可以看到私钥为
~/.ssh/id_rsa,公钥为~/.ssh/id_rsa.pub。你需要将公钥添加到目标机器的~/.ssh/authorized_keys文件中,如下图所示:

-
你需要将你的公钥复制并粘贴到所有 Kubernetes 主节点和节点候选机器中。
-
为了确保你的 ssh 公钥认证正常工作,只需从 Ansible 机器通过 ssh 登录目标主机,且不会要求输入登录密码,如下所示:
//use ssh-agent to remember your private key and passphrase (if you set)
ansible_machine$ ssh-agent bash
ansible_machine$ ssh-add
Enter passphrase for /home/saito/.ssh/id_rsa: Identity added: /home/saito/.ssh/id_rsa (/home/saito/.ssh/id_rsa)
//logon from ansible machine to k8s machine which you copied public key
ansible_machine$ ssh 10.128.0.2
Last login: Sun Nov 5 17:05:32 2017 from 133.172.188.35.bc.googleusercontent.com
k8s-master-1$
现在你已经准备好了!让我们从头开始使用 kubespray(Ansible)设置 Kubernetes。
如何操作...
kubespray 通过 GitHub 仓库提供(github.com/kubernetes-incubator/kubespray/tags),如下图所示:

因为 kubespray 是一个 Ansible playbook,而不是二进制文件,你可以直接将最新版本(截至 2018 年 5 月,版本 2.5.0 为最新)的zip或tar.gz文件下载到你的 Ansible 机器,并使用以下命令解压:
//download tar.gz format
ansible_machine$ curl -LO https://github.com/kubernetes-incubator/kubespray/archive/v2.5.0.tar.gz
//untar
ansible_machine$ tar zxvf v2.5.0.tar.gz
//it unarchives under kubespray-2.5.0 directory
ansible_machine$ ls -F
get-pip.py kubespray-2.5.0/ v2.5.0.tar.gz
//change to kubespray-2.5.0 directory
ansible_machine$ cd kubespray-2.5.0/
维护 Ansible 清单
为了执行 Ansible playbook,你需要维护自己的清单文件,其中包含目标机器的 IP 地址:
- 在 inventory 目录下有一个示例清单文件,你可以使用以下命令复制它:
//copy sample to mycluster
ansible_machine$ cp -rfp inventory/sample inventory/mycluster
//edit hosts.ini
ansible_machine$ vi inventory/mycluster/hosts.ini
-
在本手册中,我们使用的目标机器具有以下 IP 地址:
-
Kubernetes 主节点:
10.128.0.2 -
Kubernetes 节点:
10.128.0.4
-
-
在这种情况下,
hosts.ini应该采用以下格式:

- 请更改 IP 地址以匹配你的环境。
请注意,主机名(my-master-1和my-node-1)将由 kubespray playbook 根据此hosts.ini文件设置,因此可以自由分配有意义的主机名。
运行 Ansible 临时命令来测试你的环境
在运行 kubespray playbook 之前,让我们检查一下 hosts.ini 和 Ansible 本身是否正常工作:
- 为了做到这一点,使用 Ansible 临时命令,使用 ping 模块,如下图所示:

- 这个结果表示
SUCCESS。但如果你看到以下错误,可能是 IP 地址错误或目标机器宕机,请首先检查目标机器:

- 接下来,检查你的权限是否能在目标机器上提升特权。换句话说,检查是否可以运行
sudo。这是因为你需要安装 Kubernetes、Docker 及一些相关的二进制文件和配置,这些操作需要 root 权限。为了确认这一点,添加-b(提升)选项,如下图所示:

- 使用
-b选项时,实际上它会尝试在目标机器上执行 sudo 操作。如果看到SUCCESS,说明一切顺利!接下来请查看工作原理部分以运行 kubespray。
如果您不幸看到一些错误,请参考以下部分来解决 Ansible 问题。
Ansible 故障排除
理想情况下,最好使用相同的 Linux 发行版、版本、设置和登录用户。然而,由于策略、兼容性等原因,环境可能会有所不同。Ansible 非常灵活,可以支持多种使用情况来运行ssh和sudo。
需要指定 sudo 密码
根据您的 Linux 机器设置,在添加-b选项时,您可能会看到以下错误。在这种情况下,您需要在运行sudo命令时输入密码:

在这种情况下,添加-K(请求sudo密码)并重新运行。当运行 Ansible 命令时,它会要求输入您的 sudo 密码,如下图所示:

如果您的 Linux 使用su命令而不是sudo,可以在运行 Ansible 命令时添加--become-method=su来解决问题。请查阅 Ansible 文档了解更多详细信息:docs.ansible.com/ansible/latest/become.html
需要指定不同的 ssh 登录用户
有时您可能需要使用不同的登录用户 ssh 到目标机器。在这种情况下,您可以在hosts.ini中为每个主机添加ansible_user参数。例如:
-
使用用户名
kirito通过ssh登录到my-master-1 -
使用用户名
asuna通过ssh登录到my-node-1
在这种情况下,修改hosts.ini,如下所示:
my-master-1 ansible_ssh_host=10.128.0.2 ansible_user=kirito
my-node-1 ansible_ssh_host=10.128.0.4 ansible_user=asuna
需要更改 ssh 端口
另一种情况是,您可能需要在某个特定端口号而非默认端口号22上运行 ssh 守护进程。Ansible 也支持这种情况,并在hosts.ini中为每个主机使用ansible_port参数,如下所示(在此示例中,ssh守护进程在my-node-1的10022端口上运行):
my-master-1 ansible_ssh_host=10.128.0.2
my-node-1 ansible_ssh_host=10.128.0.4 ansible_port=10022
常见的 ansible 问题
Ansible 非常灵活,能够支持其他各种情况。如果您需要任何特定参数来定制目标主机的 ssh 登录,请查阅 Ansible 的清单文档,找到具体的参数:docs.ansible.com/ansible/latest/intro_inventory.html
此外,Ansible 在kubespray目录上方有一个配置文件ansible.cfg,用于定义 Ansible 的常见设置。例如,如果您使用一个非常长的用户名,通常会导致 Ansible 错误,可以修改ansible.cfg文件,将control_path设置为解决此问题,如下所示:
[ssh_connection]
control_path = %(directory)s/%%h-%%r
如果你计划设置超过10个节点,可能需要增加 ssh 同时会话数。在这种情况下,添加forks参数的同时,还需要通过添加超时参数将 ssh 超时时间从10秒增加到30秒,如下所示:
[ssh_connection]
forks = 50
timeout = 30
以下截图包含了ansible.cfg中所有之前的配置:

欲了解更多详细信息,请访问 Ansible 配置文档:docs.ansible.com/ansible/latest/intro_configuration.html
它是如何工作的...
现在你可以开始运行 kubepray playbook:
-
你已经创建了一个库存文件
inventory/mycluster/hosts.ini。除了hosts.ini,你还需要检查并更新位于inventory/mycluster/group_vars/all.yml中的全局变量配置文件。 -
定义了很多变量,但至少有一个变量
bootstrap_os需要从none更改为你的目标 Linux 机器。如果你使用的是 RHEL/CentOS7,设置bootstrap_os为centos。如果你使用的是 Ubuntu 16.04 LTS,设置bootstrap_os为ubuntu,如以下截图所示:

你还可以更新其他变量,例如 kube_version,以更改或安装 Kubernetes 版本。更多详情,请阅读文档:github.com/kubernetes-incubator/kubespray/blob/master/docs/vars.md。
-
最后,你可以执行 playbook。使用
ansible-playbook命令,而不是 Ansible 命令。ansible-playbook 会根据 playbook 中定义的任务和角色运行多个 Ansible 模块。 -
要运行 kubespray playbook,输入带有以下参数的 ansible-playbook 命令:
//use –b (become), -i (inventory) and specify cluster.yml as playbook
$ ansible-playbook -b -i inventory/mycluster/hosts.ini cluster.yml
ansible-playbook 参数与 Ansible 命令相同。所以,如果你需要使用 -K(要求输入 sudo 密码)或 --become-method=su,你也需要为 ansible-playbook 指定这些参数。
- 根据机器配置和网络带宽,完成大约需要 5 到 10 分钟。但最终你可以看到
PLAY RECAP,如以下截图所示,以查看是否成功:

- 如果你看到
failed=0,就像在前面的截图中一样,你已经成功设置了 Kubernetes 集群。你可以通过 ssh 登录到 Kubernetes 主机,并运行/usr/local/bin/kubectl命令查看状态,如下图所示:

-
上述截图显示你已成功设置 Kubernetes 版本 1.10.2 的主节点和工作节点。你可以继续使用
kubectl命令在接下来的章节中配置你的 Kubernetes 集群。 -
不幸的是,如果你看到失败计数大于 0,可能是 Kubernetes 集群没有正确设置。因为失败的原因有很多,没有单一的解决方案。建议你添加
-v选项以查看 Ansible 的详细输出,如以下代码所示:
//use –b (become), -i (inventory) and –v (verbose)
$ ansible-playbook -v -b -i inventory/mycluster/hosts.ini cluster.yml
-
如果失败是由于超时,重新运行 ansible-playbook 命令可能会解决问题。因为 Ansible 设计为幂等性的,如果你重复执行 ansible-playbook 命令一次或多次,Ansible 仍然可以正确配置。
-
如果失败是因为在运行 ansible-playbook 后更改目标 IP 地址(例如,重新使用 Ansible 机器设置另一个 Kubernetes 集群),你需要清理事实缓存文件。它位于
/tmp目录下,你只需要删除该文件,如下图所示:

另见
本节描述了如何使用 kubespray 在 Linux 操作系统上设置 Kubernetes 集群。它是一个支持主流 Linux 发行版的 Ansible playbook。Ansible 简单易用,但由于它支持各种情况和环境,你需要关注一些不同的用例。尤其是在处理与 ssh 和 sudo 相关的配置时,你需要深入了解 Ansible,以便更好地适应你的环境。
在 Kubernetes 中运行第一个容器
恭喜!你已经在之前的教程中成功构建了自己的 Kubernetes 集群。现在,让我们开始运行你的第一个容器,nginx(nginx.org/),它是一个开源的反向代理服务器、负载均衡器和 Web 服务器。通过本教程,你将创建一个简单的 nginx 应用并将其暴露给外部世界。
准备就绪
在你开始在 Kubernetes 中运行第一个容器之前,最好检查一下你的集群是否处于健康状态。以下检查清单可以确保你的 kubectl 子命令稳定运行并成功执行,避免因后台服务问题导致的未知错误:
- 检查主节点守护进程。检查 Kubernetes 组件是否正在运行:
// get the components status
$ kubectl get cs
NAME STATUS MESSAGE ERROR
controller-manager Healthy ok
scheduler Healthy ok
etcd-0 Healthy {"health": "true"}
- 检查 Kubernetes 主节点的状态:
// check if the master is running
$ kubectl cluster-info
Kubernetes master is running at https://192.168.122.101:6443
KubeDNS is running at https://192.168.122.101:6443/api/v1/namespaces/kube-system/services/kube-dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
- 检查所有节点是否准备好:
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
ubuntu01 Ready master 20m v1.10.2
ubuntu02 Ready <none> 2m v1.10.2
理想的结果应该类似于前面展示的输出。你可以成功执行 kubectl 命令并得到正确的响应。如果其中任何一项检查失败,请根据你使用的管理工具,检查前面教程中的设置。
- 检查 Docker 镜像仓库的访问权限,因为我们将使用官方的免费镜像作为示例。如果你想运行自己的应用,确保首先将其 Docker 化!你需要为自定义应用编写 Dockerfile(
docs.docker.com/engine/reference/builder/),然后构建并推送到公共或私有的 Docker 仓库中。
测试节点与公共/私有 Docker 仓库的连接
在你的节点上,尝试使用 Docker pull nginx 命令测试是否能够从 Docker Hub 拉取镜像。如果你处于代理后面,请将HTTP_PROXY添加到你的 Docker 配置文件中(docs.docker.com/engine/admin/systemd/#httphttps-proxy)。如果你希望从 Docker Hub 的私有仓库中运行镜像,或者从私有 Docker 注册表中运行镜像,则需要 Kubernetes 密钥。有关说明,请查看与密钥一起工作,在第二章中,通过 Kubernetes 概念工作。
如何操作...
我们将使用官方的 nginx Docker 镜像作为示例。该镜像可以在 Docker Hub(store.docker.com/images/nginx)以及 Docker Store(hub.docker.com/_/nginx/)中找到。
许多官方和公开的镜像都可以在 Docker Hub 或 Docker Store 上找到,这样你就不需要从头开始构建它们。只需拉取它们,并在其基础上进行自定义设置。
Docker Store 与 Docker Hub
正如你所知道的,Docker Hub 是一个更为熟悉的官方仓库,旨在为社区提供基础镜像的共享。与 Docker Hub 相比,Docker Store 更专注于企业应用。它为企业级 Docker 镜像提供了一个平台,这些镜像可能是免费或付费的软件。你可能会在使用 Docker Store 上更可靠的镜像时感到更有信心。
运行 HTTP 服务器(nginx)
在 Kubernetes 主节点上,我们可以使用kubectl run命令创建一定数量的容器。然后,Kubernetes 主节点会调度 Pod 到节点上运行,命令格式如下:
$ kubectl run <replication controller name> --image=<image name> --replicas=<number of replicas> [--port=<exposing port>]
以下示例将从 nginx 镜像创建两个副本,名称为my-first-nginx,并暴露端口80。我们可以在所谓的 Pod 中部署一个或多个容器。在此情况下,我们将每个 Pod 中部署一个容器。就像正常的 Docker 行为一样,如果 nginx 镜像在本地不存在,它将默认从 Docker Hub 拉取:
// run a deployment with 2 replicas for the image nginx and expose the container port 80
$ kubectl run my-first-nginx --image=nginx --replicas=2 --port=80
deployment "my-first-nginx" created
部署名称
在同一个 Kubernetes 命名空间中,资源(Pod、服务、部署等)不能重复。如果你执行上述命令两次,以下错误将会弹出:
Error from server (AlreadyExists): deployments.extensions "my-first-nginx" already exists
让我们继续,使用kubectl get pods查看所有 Pod 的当前状态。通常,Pod 的状态会在 Pending 状态下停留一段时间,因为节点需要一些时间从注册表拉取镜像:
// get all pods
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
my-first-nginx-7dcd87d4bf-jp572 1/1 Running 0 7m
my-first-nginx-7dcd87d4bf-ns7h4 1/1 Running 0 7m
如果 Pod 状态长时间未运行
您可以随时使用 kubectl get pods 来检查 pods 的当前状态,使用 kubectl describe pods $pod_name 来查看 pod 的详细信息。如果您在镜像名称中拼写错误,可能会收到 ErrImagePull 错误信息;如果您在没有正确凭据的情况下从私有仓库或注册表拉取镜像,可能会收到 ImagePullBackOff 信息。如果您长时间获得 Pending 状态并查看节点容量,请确保您没有运行超过节点容量的过多副本。如果出现其他意外错误信息,您可以停止 pods 或整个复制控制器,迫使主节点重新调度任务。
您还可以检查有关部署的详细信息,以查看所有的 pods 是否已准备好:
// check the status of your deployment
$ kubectl get deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
my-first-nginx 2 2 2 2 2m
暴露端口以供外部访问
我们可能还希望为 nginx 部署创建一个外部 IP 地址。在支持外部负载均衡器的云提供商(例如 Google Compute Engine)上,使用 LoadBalancer 类型将为外部访问配置一个负载均衡器。另一方面,即使您不在支持外部负载均衡器的平台上运行,您仍然可以通过创建 Kubernetes 服务来暴露端口,具体方法如下。稍后我们将描述如何进行外部访问:
// expose port 80 for replication controller named my-first-nginx
$ kubectl expose deployment my-first-nginx --port=80 --type=LoadBalancer
service "my-first-nginx" exposed
我们可以看到我们刚刚创建的服务状态:
// get all services
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2h
my-first-nginx LoadBalancer 10.102.141.22 <pending> 80:31620/TCP 3m
如果服务守护进程作为容器运行(例如,使用 kubeadm 作为管理工具),您可能会看到一个额外的名为 kubernetes 的服务。它用于内部暴露 Kubernetes API 服务器的 REST API。my-first-nginx 服务外部 IP 的挂起状态表示它正在等待来自云提供商的特定公共 IP。请查看 第六章,在 AWS 上构建 Kubernetes,和 第七章,在 GCP 上构建 Kubernetes,以获取更多细节。
恭喜!您刚刚通过 Kubernetes pod 运行了第一个容器,并通过 Kubernetes 服务暴露了端口 80。
停止应用程序
我们可以使用诸如删除部署和服务的命令来停止应用程序。在此之前,我们建议您先阅读以下代码,以便更好地理解其工作原理:
// stop deployment named my-first-nginx
$ kubectl delete deployment my-first-nginx
deployment.extensions "my-first-nginx" deleted
// stop service named my-first-nginx
$ kubectl delete service my-first-nginx
service "my-first-nginx" deleted
它是如何工作的……
让我们通过 kubectl 命令中的 describe 查看服务的洞察。我们将创建一个类型为 LoadBalancer 的 Kubernetes 服务,将流量分发到两个端点 192.168.79.9 和 192.168.79.10,端口为 80:
$ kubectl describe service my-first-nginx
Name: my-first-nginx
Namespace: default
Labels: run=my-first-nginx
Annotations: <none>
Selector: run=my-first-nginx
Type: LoadBalancer
IP: 10.103.85.175
Port: <unset> 80/TCP
TargetPort: 80/TCP
NodePort: <unset> 31723/TCP
Endpoints: 192.168.79.10:80,192.168.79.9:80
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
这里的端口是一个抽象的服务端口,它将允许集群内的任何其他资源访问该服务。nodePort 将指示外部端口以允许外部访问。targetPort 是容器允许流量进入的端口;默认情况下,它将与服务端口相同。
在下面的图示中,外部访问将通过 nodePort 访问服务。服务充当负载均衡器,将流量分发到使用端口 80 的 Pod。然后,Pod 会将流量传递到相应的容器,通过 targetPort 80:

在任何节点或主节点上,一旦设置了互连网络,你应该能够使用 ClusterIP 192.168.61.150 和端口 80 访问 nginx 服务:
// curl from service IP
$ curl 10.103.85.175:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
如果我们直接 curl 到 Pod 的目标端口,结果是一样的:
// curl from endpoint, the content is the same as previous nginx html
$ curl 192.168.79.10:80
<!DOCTYPE html>
<html>
...
如果你想尝试外部访问,可以使用浏览器访问外部 IP 地址。请注意,外部 IP 地址取决于你所在的环境。
在 Google 计算引擎中,你可以通过 ClusterIP 访问,并设置适当的防火墙规则:
$ curl http://<clusterIP>
在自定义环境中,例如本地数据中心,你可以通过节点的 IP 地址访问:
$ curl http://<nodeIP>:<nodePort>
你应该能够使用网页浏览器看到以下页面:

另见
我们在本节中运行了我们的第一个容器。继续阅读下一章,获取更多关于 Kubernetes 的知识:
- 第二章,深入了解 Kubernetes 概念
第二章:深入了解 Kubernetes 概念
本章将介绍以下操作:
-
链接 Pods 和容器
-
使用 ReplicaSets 管理 Pods
-
部署 API
-
使用服务(Services)
-
使用卷(Volumes)
-
使用 Secrets
-
使用名称
-
使用命名空间(Namespaces)
-
使用标签和选择器
简介
本章将从在 Kubernetes 系统上创建不同类型的资源开始。为了实现你的微服务结构应用,阅读本章的操作将是理解 Kubernetes 资源概念并加深理解的一个好开始。部署应用到 Kubernetes 后,你可以继续进行可扩展且高效的容器管理,同时也能完成微服务的 DevOps 交付流程。
Kubernetes 概述
使用 Kubernetes 非常简单,可以通过命令行接口(CLI)或 API(RESTful)进行操作。本节将介绍如何通过 CLI 控制 Kubernetes。本章节使用的 CLI 版本是 1.10.2。
安装 Kubernetes master 后,你可以运行如下的kubectl命令。它显示了 kubectl 和 Kubernetes master 的版本(API 服务器和 CLI 版本都是 v1.10.2):
$ kubectl version --short
Client Version: v1.10.2
Server Version: v1.10.2
kubectl通过 RESTful API 连接 Kubernetes API 服务器。默认情况下,如果.kube/config没有配置,它会尝试访问本地主机,否则你需要使用--server参数指定 API 服务器地址。因此,建议在 API 服务器机器上使用kubectl进行实践。
如果你通过网络使用 kubectl,你需要考虑 API 服务器的身份验证和授权。请参阅第七章,在 GCP 上构建 Kubernetes。
kubectl是 Kubernetes 集群的唯一命令,它控制 Kubernetes 集群管理器。更多信息请访问kubernetes.io/docs/user-guide/kubectl-overview/。任何容器或 Kubernetes 集群操作都可以通过kubectl命令执行。
此外,kubectl 允许通过命令行的可选参数或文件(使用-f选项)输入信息;强烈建议使用文件,因为你可以将 Kubernetes 配置作为代码进行维护。本章将详细介绍这一点。
这是一个典型的kubectl命令行参数:
kubectl [command] [TYPE] [NAME] [flags]
前述命令的属性如下:
-
command:指定你希望对一个或多个资源执行的操作。 -
TYPE:指定资源类型。资源类型区分大小写,你可以指定单数、复数或缩写形式。 -
NAME:指定资源的名称。名称区分大小写。如果省略名称,则会显示所有资源的详细信息。 -
flags:指定可选的标志。
例如,如果你想启动 nginx,可以使用 kubectl run 命令或使用带有 YAML 文件的 kubectl create -f 命令,如下所示:
- 使用
run命令:
$ kubectl run my-first-nginx --image=nginx "my-first-nginx"
- 使用带有 YAML 文件的
create -f命令:
$ cat nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-first-nginx
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
//specify -f (filename)
$ kubectl create -f nginx.yaml
deployment.apps "my-first-nginx" created
- 如果你想查看 Deployment 的状态,请输入
kubectl get命令,如下所示:
$ kubectl get deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
my-first-nginx 1 1 1 1 4s
- 如果你还希望支持缩写,请输入以下内容:
$ kubectl get deploy
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
my-first-nginx 1 1 1 1 38s
- 如果你想删除这些资源,请输入
kubectl delete命令,如下所示:
$ kubectl delete deploy my-first-nginx
deployment.extensions "my-first-nginx" deleted
kubectl命令支持多种子命令;使用-h选项查看详细信息,例如:
//display whole sub command options
$ kubectl -h
//display sub command "get" options
$ kubectl get -h
//display sub command "run" options
$ kubectl run -h
本节描述了如何使用 kubectl 命令来控制 Kubernetes 集群。以下食谱描述了如何设置 Kubernetes 组件:
-
使用 minikube 在 macOS 上设置 Kubernetes 集群 和 使用 minikube 在 Windows 上设置 Kubernetes 集群,参见第一章,构建你自己的 Kubernetes 集群
-
使用 kubeadm 在 Linux 上设置 Kubernetes 集群,参见第一章,构建你自己的 Kubernetes 集群
-
使用 kubespray(Ansible)在 Linux 上设置 Kubernetes 集群,参见第一章,构建你自己的 Kubernetes 集群
链接 Pods 和容器
Pod 是一个或多个容器的集合,是 Kubernetes 中最小的可部署单元。Pod 总是被共同定位和共同调度,并在共享上下文中运行。每个 Pod 都由以下 Linux 命名空间进行隔离:
-
进程 ID(PID)命名空间
-
网络命名空间
-
进程间通信(IPC)命名空间
-
Unix 时间共享(UTS)命名空间
在容器化之前,它们会在同一台物理机或虚拟机上执行。
构建你自己的应用栈 Pod(例如,Web 服务器和数据库),它们由不同的 Docker 镜像混合而成是很有用的。
准备就绪
你必须拥有一个 Kubernetes 集群,并确保 Kubernetes 节点能够访问 Docker Hub(hub.docker.com)以便下载 Docker 镜像。
如果你正在运行 minikube,首先使用 minikube ssh 登录到 minikube 虚拟机,然后运行 docker pull 命令。
你可以通过以下方式使用 docker pull 命令模拟下载 Docker 镜像:
//this step only if you are using minikube
$ minikube ssh
_ _
_ _ ( ) ( )
___ ___ (_) ___ (_)| |/') _ _ | |_ __
/' _ ` _ `\| |/' _ `\| || , < ( ) ( )| '_`\ /'__`\
| ( ) ( ) || || ( ) || || |\`\ | (_) || |_) )( ___/
(_) (_) (_)(_)(_) (_)(_)(_) (_)`\___/'(_,__/'`\____)
//run docker pull to download CentOS docker image
$ docker pull centos
Using default tag: latest
latest: Pulling from library/centos
d9aaf4d82f24: Pull complete
Digest: sha256:4565fe2dd7f4770e825d4bd9c761a81b26e49cc9e3c9631c58cfc3188be9505a
Status: Downloaded newer image for centos:latest
如何操作...
以下是创建包含 2 个容器的 Pod 的步骤:
-
登录到 Kubernetes 机器(如果使用 minikube,则无需登录),并准备以下 YAML 文件。它定义了启动
nginx容器和 CentOS 容器。 -
nginx容器打开 HTTP 端口(TCP/80)。另一方面,CentOS 容器每三秒钟尝试使用curl命令访问localhost:80:
$ cat my-first-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-first-pod
spec:
containers:
- name: my-nginx
image: nginx
- name: my-centos
image: centos
command: ["/bin/sh", "-c", "while : ;do curl http://localhost:80/; sleep 10; done"]
- 然后,执行
kubectl create命令以启动my-first-pod,如下所示:
$ kubectl create -f my-first-pod.yaml
pod "my-first-pod" created
这需要几秒钟到几分钟的时间,具体取决于 Docker Hub 的网络带宽和 Kubernetes 节点的规格。
- 你可以通过
kubectl get pods查看 Pod 的状态,如下所示:
//still downloading Docker images (0/2)
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
my-first-pod 0/2 ContainerCreating 0 14s
//my-first-pod is running (2/2)
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
my-first-pod 2/2 Running 0 1m
现在,nginx 容器(my-nginx)和 CentOS 容器(my-centos)都已经准备好。
- 让我们检查一下 CentOS 容器是否能访问
nginx。你可以运行kubectl exec命令在 CentOS 容器上运行 bash,然后使用curl命令访问nginx,如下所示:
//run bash on my-centos container
//then access to TCP/80 using curl
$ kubectl exec my-first-pod -it -c my-centos -- /bin/bash
[root@my-first-pod /]#
[root@my-first-pod /]# curl -L http://localhost:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
如你所见,Pod 将两个不同的容器,nginx 和 CentOS,连接到同一个 Linux 网络命名空间中。
它是如何工作的...
启动 Pod 时,Kubernetes 调度器会将任务分配给 kubelet 进程,处理在一个 Kubernetes 节点上启动 nginx 和 CentOS 容器的所有操作。
下图展示了这两个容器和 Pod;这两个容器可以通过 localhost 网络进行通信,因为在 Pod 内的容器共享网络接口:

一个 Pod 有两个容器,它们可以通过 localhost 进行通信。
如果你有两个或更多节点,可以检查 -o wide 选项以找到运行某个 Pod 的节点:
//it indicates Node "minikube" runs my-first-pod
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
my-first-pod 2/2 Running 0 43m 172.17.0.2 minikube
登录到该节点,然后可以使用 docker ps | grep my-first-pod 命令查看正在运行的容器,如下所示:

属于 my-first-pod 的容器列表
你可能会注意到 my-first-pod 包含三个容器;centos、nginx 和 pause 容器都在运行,而不是两个。因为每个 Pod 都属于一个特定的 Linux 命名空间,如果 CentOS 和 nginx 容器都死掉了,命名空间也会被销毁。因此,pause 容器会保留在 Pod 中,以维持 Linux 命名空间。
让我们启动第二个 Pod,将其重命名为 my-second-pod,并运行 kubectl create 命令,如下所示:
//just replace the name from my-first-pod to my-second-pod
$ cat my-first-pod.yaml | sed -e 's/my-first-pod/my-second-pod/' > my-second-pod.yaml
//metadata.name has been changed to my-second-pod
$ cat my-second-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-second-pod
spec:
containers:
- name: my-nginx
image: nginx
- name: my-centos
image: centos
command: ["/bin/sh", "-c", "while : ;do curl
http://localhost:80/; sleep 10; done"]
//create second pod
$ kubectl create -f my-second-pod.yaml
pod "my-second-pod" created
//2 pods are running
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
my-first-pod 2/2 Running 0 1h
my-second-pod 2/2 Running 0 43s
现在你有两个 Pod;每个 Pod 有两个容器,centos 和 nginx。因此,在 Kubernetes 集群中总共有四个容器,如下图所示:

从 my-first-pod 复制 Pod 到 my-second-pod
如果你想部署更多相同的 Pod,可以考虑使用 Deployment(ReplicaSet)。
测试完成后,你可以运行 kubectl delete 命令从 Kubernetes 集群中删除 Pod:
//specify --all option to delete all pods
$ kubectl delete pods --all
pod "my-first-pod" deleted
pod "my-second-pod" deleted
//pods are terminating
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
my-first-pod 2/2 Terminating 0 1h
my-second-pod 2/2 Terminating 0 3m
另见
本章的这一配方描述了如何控制 Pods。它们是 Kubernetes 操作的基本组成部分。接下来的配方将描述如何使用 Deployments、Services 等进行高级 Pod 操作:
-
使用 ReplicaSets 管理 Pods
-
Deployment API
-
与服务一起工作
-
使用标签和选择器
使用 ReplicaSets 管理 Pods
ReplicaSet 是 Kubernetes 中指代 Pod 副本的 API 对象的术语。其目的是能够控制一组 Pod 的行为。ReplicaSet 确保用户指定数量的 Pod 始终在运行。如果 ReplicaSet 中的某些 Pod 崩溃并终止,系统将自动在健康节点上重新创建具有原始配置的 Pod,并保持一定数量的进程持续运行。在更改副本集大小时,用户可以轻松地扩展或缩减应用程序。根据这一特性,无论是否需要 Pod 的副本,你都可以依赖 ReplicaSet 进行自动恢复和可扩展性管理。在这个教程中,你将学习如何使用 ReplicaSet 管理 Pod:

ReplicaSet 和它们的 Pod 在两个节点上
ReplicaSet 通常处理一个应用层。正如在前面的图示中所看到的,我们启动了一个包含三个 Pod 副本的 ReplicaSet。以下是一些机制细节:
-
kube-controller-manager 守护进程帮助维持资源处于其期望的状态。例如,图示中 ReplicaSet 的期望状态是三个 Pod 副本。
-
kube-scheduler 守护进程(Kubernetes 的调度器)负责将任务分配给健康的节点。
-
ReplicaSet 的选择器用于决定它覆盖哪些 Pod。如果 Pod 标签中的键值对包含 ReplicaSet 选择器中的所有项,则该 Pod 属于该 ReplicaSet。如你所见,图示显示三个 Pod 由 ReplicaSet 管理。尽管 Pod 2 的
env标签不同,但由于其他两个标签role和project与 ReplicaSet 的选择器匹配,它仍然被选中。
ReplicationController?ReplicaSet? 对于经验丰富的 Kubernetes 用户,你可能会注意到 ReplicaSet 看起来与 ReplicationController 非常相似。自 Kubernetes 1.2 版本以来,为了集中关注不同的功能,ReplicationController 的功能已被 ReplicaSet 和 Deployment 覆盖。ReplicaSet 关注 Pod 副本,确保特定的 Pod 始终保持在健康状态。另一方面,Deployment 是一个更高级的 API,可以管理 ReplicaSet,执行应用程序的滚动更新并公开服务。在 Kubernetes v1.8.3 中,用户仍然可以创建复制控制器。然而,建议使用与 ReplicaSet 一起使用的 Deployment,因为这些是最新的,并且具有更细粒度的配置。
准备就绪
创建 ReplicaSet 与创建任何 Kubernetes 资源相同;我们在 Kubernetes 主节点上执行 kubectl 命令。因此,我们需要确保你的 Kubernetes 环境已准备好接受命令。此外,Kubernetes 节点应能够访问 Docker Hub。接下来的几页演示中,我们将以官方的 nginx Docker 镜像为例,该镜像也存储在公共 Docker 注册表中。
评估已准备好的 Kubernetes 系统:你可以通过检查这里的项目来验证你的 Kubernetes 主节点是否是一个可用的系统:
-
检查守护进程是否正在运行:主节点上应该有三个正在运行的守护进程:
apiserver、scheduler和controller-manager。 -
检查命令 kubectl 是否存在且可用:尝试使用命令
kubectl get cs来检查此项目和第一个项目。你不仅可以验证组件的状态,还可以验证kubectl的可用性。 -
检查节点是否准备好工作:你可以通过使用命令
kubectl get nodes来检查它们的状态。
如果这里列出的某些项目无效,请参考第一章,构建你自己的 Kubernetes 集群,根据你选择的安装方式获取正确的指导。
如何操作...
在本节中,我们将展示 ReplicaSet 从创建到销毁的生命周期。
创建 ReplicaSet
当尝试使用命令行立即启动 Kubernetes 服务时,我们通常会使用kubectl run。但是,它默认会创建一个 Deployment,不仅处理 Pod 副本,还提供容器更新机制。为了简单地创建一个独立的 ReplicaSet,我们可以利用一个配置 YAML 文件并执行它:
$ cat my-first-replicaset.yaml
apiVersion: extensions/v1beta1
kind: ReplicaSet
metadata:
name: my-first-replicaset
labels:
version: 0.0.1
spec:
replicas: 3
selector:
matchLabels:
project: My-Happy-Web
role: frontend
template:
metadata:
labels:
project: My-Happy-Web
role: frontend
env: dev
spec:
containers:
- name: happy-web
image: nginx:latest
上述文件是我们第一个 ReplicaSet 的 YAML 配置文件。它定义了一个名为my-first-replicaset的 ReplicaSet,具有三个 Pod 副本。标签和选择器是 ReplicaSet 最具特色的设置。这里有两组标签:一组用于 ReplicaSet,另一组用于 Pods。ReplicaSet 的第一个标签位于该资源的 metadata 下,在名称下方,主要用于描述。而位于模板 metadata 下的另一个标签值,用于 Pods,也是用于标识。ReplicaSet 管理其选择器所包含标签的 Pods。
在我们的示例配置文件中,ReplicaSet 的选择器会查找带有project: My-Happy-Web和role: frontend标签的 Pods。由于我们在此 ReplicaSet 的控制下启动 Pods,因此 Pods 的标签应该必定包含选择器所关注的标签。你在创建带有错误标签的 ReplicaSet 时可能会遇到以下错误信息:`selector` does not match template `labels`。
现在,让我们通过这个文件来创建 ReplicaSet:
$ kubectl create -f my-first-replicaset.yaml
replicaset.extensions "my-first-replicaset" created
Kubernetes v1.9 中 ReplicaSet 的 API 版本
当本书编写时,Kubernetes v1.9 已发布。ReplicaSet 的 API 版本变更为稳定版本apps/v1,不再是apps/v1beta2。如果你使用的是旧版 Kubernetes,请将apiVersion的值更改为apps/v1beta2,或者直接更新你的 Kubernetes 系统。
获取 ReplicaSet 的详细信息
在创建 ReplicaSet 后,子命令 get 和 describe 可以帮助我们获取其信息以及 Pod 的状态。在 Kubernetes 的 CLI 中,我们可以使用 rs 来代替完整的资源类型名称 ReplicaSet:
// use subcommand "get" to list all ReplicaSets
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
my-first-replicaset 3 3 3 4s
该结果大致显示 my-first-replicaset 的 Pod 副本都已成功运行;当前运行的 Pod 数量与期望的一致,并且它们都已准备好接受请求。
有关详细信息,请使用子命令 describe 查看:
// specify that we want to check ReplicaSet called my-first-replicaset
$ kubectl describe rs my-first-replicaset
Name: my-first-replicaset
Namespace: default
Selector: project=My-Happy-Web,role=frontend
Labels: version=0.0.1
Annotations: <none>
Replicas: 3 current / 3 desired
Pods Status: 3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
Labels: env=dev
project=My-Happy-Web
role=frontend
Containers:
happy-web:
Image: nginx:latest
Port: <none>
Host Port: <none>
Environment: <none>
Mounts: <none>
Volumes: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 9s replicaset-controller Created pod: my-first-replicaset-8hg55
Normal SuccessfulCreate 9s replicaset-controller Created pod: my-first-replicaset-wtphz
Normal SuccessfulCreate 9s replicaset-controller Created pod: my-first-replicaset-xcrws
你可以看到输出列出了 ReplicaSet 配置的详细信息,就像我们在 YAML 文件中请求的那样。此外,Pod 创建的日志作为 ReplicaSet 的一部分显示出来,这确认了 Pod 副本已成功创建,并且被指定了唯一名称。你也可以通过名称检查 Pod:
// get the description according the name of Pod, please look at the Pod name shown on your screen, which should be different from this book.
$ kubectl describe pod my-first-replicaset-xcrws
更改 ReplicaSet 的配置
被称为 edit、patch 和 replace 的子命令可以帮助更新 Kubernetes 中的实时资源。所有这些功能通过修改配置文件的方式来更改设置。在这里我们以 edit 为例。
子命令 edit 允许用户通过编辑器修改资源配置。尝试通过命令 kubectl edit rs $REPLICASET_NAME 更新你的 ReplicaSet;你将通过默认编辑器访问这个资源,编辑一个 YAML 配置文件:
// demonstrate to change the number of Pod replicas.
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
my-first-replicaset 3 3 3 2m
// get in the editor, modify the replica number, then save and leave
$ kubectl edit rs my-first-replicaset
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: extensions/v1beta1
kind: ReplicaSet
metadata:
creationTimestamp: 2018-05-05T20:48:38Z
generation: 1
labels:
version: 0.0.1
name: my-first-replicaset
namespace: default
resourceVersion: "1255241"
selfLink: /apis/extensions/v1beta1/namespaces/default/replicasets/my-first-replicaset
uid: 18330fa8-cd55-11e7-a4de-525400a9d353
spec:
replicas: 4
selector:
matchLabels:
...
replicaset "my-first-replicaset" edited
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
my-first-replicaset 4 4 4 4m
在演示中,我们成功地向该集合中添加了一个 Pod,但这不是自动扩缩容 Pod 的最佳实践。请查看第三章中处理配置文件的示例,参考其中的容器操作部分,并尝试修改其他值。
删除 ReplicaSet
为了从 Kubernetes 系统中删除 ReplicaSet,你可以依赖子命令 delete。当我们执行 delete 来删除资源时,它会强制删除目标对象:
$ time kubectl delete rs my-first-replicaset && kubectl get pod
replicaset.extensions "my-first-replicaset" deleted
real 0m2.492s
user 0m0.188s
sys 0m0.048s
NAME READY STATUS RESTARTS AGE
my-first-replicaset-8hg55 0/1 Terminating 0 53m
my-first-replicaset-b6kr2 1/1 Terminating 0 48m
my-first-replicaset-wtphz 0/1 Terminating 0 53m
my-first-replicaset-xcrws 1/1 Terminating 0 53m
我们发现响应时间非常短,效果也立即体现。
删除 ReplicaSet 下的 Pod 如前所述,无法通过删除 Pod 来缩减 ReplicaSet 的规模,因为在删除 Pod 时,ReplicaSet 会处于不稳定状态:如果期望的 Pod 数量未达到,控制器管理器会要求 ReplicaSet 创建另一个 Pod。这个概念可以通过以下命令展示:
// check ReplicaSet and the Pods
$ kubectl get rs,pod
NAME DESIRED CURRENT READY AGE
rs/my-first-replicaset 3 3 3 14s
NAME READY STATUS RESTARTS AGE
po/my-first-replicaset-bxf45 1/1 Running 0 14s
po/my-first-replicaset-r6wpx 1/1 Running 0 14s
po/my-first-replicaset-vt6fd 1/1 Running 0 14s
// remove certain Pod and check what happened
$ kubectl delete pod my-first-replicaset-bxf45
pod "my-first-replicaset-bxf45" deleted
$ kubectl get rs,pod
NAME DESIRED CURRENT READY AGE
rs/my-first-replicaset 3 3 3 2m
NAME READY STATUS RESTARTS AGE
po/my-first-replicaset-dvbpg 1/1 Running 0 6s
po/my-first-replicaset-r6wpx 1/1 Running 0 2m
po/my-first-replicaset-vt6fd 1/1 Running 0 2m
// check the event log as well
$ kubectl describe rs my-first-replicaset
(ignored)
:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 2m replicaset-controller Created pod: my-first-replicaset-bxf45
Normal SuccessfulCreate 2m replicaset-controller Created pod: my-first-replicaset-r6wpx
Normal SuccessfulCreate 2m replicaset-controller Created pod: my-first-replicaset-vt6fd
Normal SuccessfulCreate 37s replicaset-controller Created pod: my-first-replicaset-dvbpg
你会发现,尽管 my-first-replicaset-bxf45 Pod 被删除,但 my-first-replicaset-dvbpg Pod 会自动创建并附加到该 ReplicaSet 中。
它是如何工作的...
ReplicaSet 通过使用 Pod 模板和标签来定义一组 Pod。如前面章节中的思路,ReplicaSet 仅通过标签管理 Pod。这也意味着 Pod 模板和 Pod 配置可能不同。这也意味着,可以通过修改标签将独立的 Pod 添加到集合中。
让我们通过创建一个与本配方开头的图示类似的 ReplicaSet,来评估选择器和标签的概念:

ReplicaSet 会管理具有相同标签的 Pods,这些标签在其选择器中有所描述。
首先,我们将创建一个 CentOS Pod,并为其指定标签 project: My-Happy-Web、role: frontend 和 env: test:
// use subcommand "run" with tag restart=Never to create a Pod
$ kubectl run standalone-pod --image=centos --labels="project=My-Happy-Web,role=frontend,env=test" --restart=Never --command sleep 3600
pod "standalone-pod" created
// check Pod along with the labels
$ kubectl get pod -L project -L role -L env
NAME READY STATUS RESTARTS AGE PROJECT ROLE ENV
standalone-pod 1/1 Running 0 3m My-Happy-Web frontend test
添加此命令后,带有我们指定标签的 standalone Pod 会运行。
接下来,使用 YAML 文件再次创建你的第一个 ReplicaSet 示例:
$ kubectl create -f my-first-replicaset.yaml
replicaset.apps "my-first-replicaset" created
// check the Pod again
$ kubectl get pod -L project -L role -L env
NAME READY STATUS RESTARTS AGE PROJECT ROLE ENV
my-first-replicaset-fgdc8 1/1 Running 0 14s My-Happy-Web frontend dev
my-first-replicaset-flc9m 1/1 Running 0 14s My-Happy-Web frontend dev
standalone-pod 1/1 Running 0 6m My-Happy-Web frontend test
如前述结果所示,只有两个 Pod 被创建。这是因为 Pod standalone-pod 被认为是 my-first-replicaset 所管理的一部分。请记住,my-first-replicaset 会管理带有标签 project: My-Happy-Web 和 role:frontend 的 Pod(忽略 env 标签)。去检查 standalone Pod,你会发现它也是 ReplicaSet 的一部分:
$ kubectl describe pod standalone-pod
Name: standalone-pod
Namespace: default
Node: ubuntu02/192.168.122.102
Start Time: Sat, 05 May 2018 16:57:14 -0400
Labels: env=test
project=My-Happy-Web
role=frontend
Annotations: <none>
Status: Running
IP: 192.168.79.57
Controlled By: ReplicaSet/my-first-replicaset ...
类似地,一旦我们删除该 ReplicaSet,独立的 Pod 也将与其一起被移除:
// remove the ReplicaSet and check pods immediately
$ kubectl delete rs my-first-replicaset && kubectl get pod
replicaset.extensions "my-first-replicaset" deleted
NAME READY STATUS RESTARTS AGE
my-first-replicaset-fgdc8 0/1 Terminating 0 1m
my-first-replicaset-flc9m 0/1 Terminating 0 1m
standalone-pod 0/1 Terminating 0 7m
还有更多…
Kubernetes 提供了多种 Pod 管理资源。鼓励用户利用不同类型的资源以满足不同的需求。让我们将以下列出的资源类型与 ReplicaSet 进行比较:
-
Deployment:在一般情况下,Kubernetes Deployment 与 ReplicaSet 一起使用,以实现完整的 Pod 管理:容器滚动更新、负载均衡和服务暴露。
-
Job:有时,我们希望 Pod 作为作业而不是服务运行。Kubernetes 的 Job 适用于这种情况。可以把它看作是一个带有终止约束的 ReplicaSet。
-
DaemonSet:与 ReplicaSet 不同,Kubernetes DaemonSet 保证在集群中的每个节点上都运行指定的 Pod。换句话说,它是在每个节点上部署 ReplicaSet 的一个子集。
想要获得更多的想法和指导,可以查看 第三章中的配方 确保容器的灵活使用,《玩转容器》。
另请参见
现在你已经理解了 ReplicaSet 的概念。继续查阅本章中的其他配方,探索更多 Kubernetes 资源,进一步体验 ReplicaSet 的神奇效果:
-
部署 API
-
使用服务
-
使用标签和选择器
此外,由于你已经使用配置文件创建了一个简单的 ReplicaSet,请参考有关为 Kubernetes 资源创建自定义配置文件的更多详细信息:
- 配置文件操作 部分在 第三章,《玩转容器》
部署 API
Deployment API 是在 Kubernetes 1.2 版本中引入的。它取代了复制控制器。复制控制器的滚动更新和回滚功能是通过客户端(kubectl命令和REST API)来实现的,kubectl需要在更新复制控制器时保持连接。另一方面,Deployments 在服务器端处理滚动更新和回滚的过程。一旦请求被接受,客户端可以立即断开连接。
因此,Deployments API 被设计为一个更高层次的 API 来管理 ReplicaSet 对象。本节将探讨如何使用 Deployments API 来管理 ReplicaSets。
准备工作
为了创建 Deployment 对象,通常使用kubectl run命令,或者准备描述 Deployment 配置的 YAML/JSON 文件。这个示例使用kubectl run命令来创建my-nginx Deployment 对象:
//create my-nginx Deployment (specify 3 replicas and nginx version 1.11.0)
$ kubectl run my-nginx --image=nginx:1.11.0 --port=80 --replicas=3
deployment.apps "my-nginx" created
//see status of my-nginx Deployment
$ kubectl get deploy
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
my-nginx 3 3 3 3 8s
//see status of ReplicaSet
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
my-nginx-5d69b5ff7 3 3 3 11s
//see status of Pod
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
my-nginx-5d69b5ff7-9mhbc 1/1 Running 0 14s
my-nginx-5d69b5ff7-mt6z7 1/1 Running 0 14s
my-nginx-5d69b5ff7-rdl2k 1/1 Running 0 14s
如你所见,Deployment 对象my-nginx创建了一个ReplicaSet,它有一个标识符:<Deployment name>-<hex decimal hash>。然后 ReplicaSet 创建了三个 Pod,它们的标识符为:<ReplicaSet id>-<random id>。
在 Kubernetes v1.8 版本之前,<Deployment name>-<pod-template-hash value (number)> 被用作 ReplicaSet 的标识符,而不是十六进制哈希值。
更多细节,请查看拉取请求:github.com/kubernetes/kubernetes/pull/51538。
该图展示了Deployment、ReplicaSet和Pod之间的关系:

Deployment、ReplicaSet 和 Pod 的关系图
由于这种关系,如果你对my-nginx Deployment 对象执行delete操作,它也会尝试删除相应的 ReplicaSet 和 Pods:
//delete my-nginx Deployment
$ kubectl delete deploy my-nginx
deployment.extensions "my-nginx" deleted
//see status of ReplicaSet
$ kubectl get rs
No resources found.
//see status of Pod, it has been terminated
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
my-nginx-5d69b5ff7-9mhbc 0/1 Terminating 0 2m
my-nginx-5d69b5ff7-mt6z7 0/1 Terminating 0 2m
my-nginx-5d69b5ff7-rdl2k 0/1 Terminating 0 2m
这个示例只是一个简单的create和delete,此时可以轻松理解 Deployment 对象和 ReplicaSet 对象之间 1:1 的关系。然而,Deployment 对象可以管理多个 ReplicaSets 来保存历史记录。所以,实际的关系是 1:N,如下图所示:

Deployments 维护 ReplicaSet 的历史记录
为了理解 1:N 关系,让我们重新创建这个 Deployment 对象,并进行一些操作,看看 Deployment 如何管理 ReplicaSet 的历史记录。
如何操作...
你可以运行kubectl run命令来重新创建my-nginx,或者编写一个 Deployment 配置文件来实现相同的结果。这是一个很好的机会来学习 Deployment 配置文件。
这个示例等同于 kubectl run my-nginx --image=nginx:1.11.0 --port=80 --replicas=3:
$ cat deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
replicas: 3
selector:
matchLabels:
run: my-nginx
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx:1.11.0
ports:
- containerPort: 80
这些参数按键值对排序,具体描述如下:
| Key | Value | Description |
|---|---|---|
apiVersion |
apps/v1 |
在 Kubernetes v1.8 之前,使用的是 apps/v1Beta1,v1.8 使用的是 apps/v1Beta2,之后 v1.9 及更高版本使用 apps/v1 |
kind |
deployment |
表示这是一个 Deployment 配置集 |
metadata.name |
my-nginx |
Deployment 的名称 |
spec.replicas |
3 |
希望有三个 Pod |
spec.selector.matchLabels |
run:my-nginx |
控制具有此标签的 ReplicaSet/Pods |
spec.template.metadata.labels |
run:my-nginx |
创建 ReplicaSet/Pod 时分配此标签;必须与spec.selector.matchLabels匹配 |
| spec.template.spec.containers | name: my-nginximage: nginx:1.11.0port:- containerPort:80 | ReplicaSet 创建和管理包含以下内容的 Pods:
-
名称为
my-nginx -
容器镜像为 nginx 版本 1.11.0
-
发布端口号
80
|
如果你使用这个 YAML 文件来创建 Deployment,应该使用kubectl create命令,而不是kubectl run。
请注意,此时,你还应指定--save-config,这将允许你以后使用kubectl apply命令更新资源。此外,指定--record可以存储命令行历史记录。这两个选项并非管理 ReplicaSet 历史记录的必需,但有助于你更好地保存信息:
//use -f to specify YAML file
$ kubectl create -f deploy.yaml --save-config --record
deployment.apps "my-nginx" created
//check my-nginx Deployment
$ kubectl get deploy
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
my-nginx 3 3 3 3 5s
$ kubectl describe deploy my-nginx
Name: my-nginx
Namespace: default
CreationTimestamp: Wed, 09 May 2018 03:40:09 +0000
Labels: <none>
Annotations: deployment.kubernetes.io/revision=1
kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"name":"my-nginx","namespace":"default"},"spec":{"replicas":3,"selector":{"mat...
kubernetes.io/change-cause=kubectl create --filename=deploy.yaml --save-config=true --record=true
Selector: run=my-nginx
Replicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: run=my-nginx
Containers:
my-nginx:
Image: nginx:1.11.0
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: my-nginx-54bb7bbcf9 (3/3 replicas created) Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 34s deployment-controller Scaled up replica set my-nginx-54bb7bbcf9 to 3
你可以在前面的代码中看到OldReplicaSets和NewReplicaSet属性,它们表示 Deployment 和 ReplicaSet 之间的某些关联。
每当你更新容器模板的定义时,例如,将 nginx 镜像版本从 1.11.0 更改为 1.12.0,Deployment my-nginx将创建一个新的 ReplicaSet。然后,NewReplicaSet属性将指向使用 nginx 版本 1.12.0 的新 ReplicaSet。
另一方面,OldReplicaSets属性指向一个旧的 ReplicaSet,该 ReplicaSet 使用的是 nginx 版本 1.11.0,直到新的 ReplicaSet 完成并设置新的 Pod。
这些旧的/新的 ReplicaSet 与 Deployment 之间的关联,Kubernetes 管理员可以轻松地进行回滚操作,以防新的 ReplicaSet 出现问题。
此外,Deployment 可以保留与其之前关联的 ReplicaSet 的历史记录。因此,Deployment 可以随时回滚到任何旧的 ReplicaSet。
它是如何工作的...
如前所述,假设我们将 nginx 镜像版本从 1.11.0 升级到 1.12.0。有两种方法可以更改容器镜像:使用kubectl set命令,或更新 YAML 文件然后使用kubectl apply命令。
使用kubectl set命令更快捷,并且当使用--record选项时可提供更好的可视化效果。
另一方面,更新 YAML 并使用kubectl apply命令更适合保留整个 Deployment YAML 配置文件,在使用版本控制系统(如git)时更为有利。
使用 kubectl set 更新容器镜像
使用kubectl set命令允许我们覆盖spec.template.spec.containers[].image属性,这类似于使用kubectl run命令指定镜像文件。以下示例指定my-nginx Deployment,将容器my-nginx的镜像更改为 nginx 版本 1.12.0:
$ kubectl set image deployment my-nginx my-nginx=nginx:1.12.0 --record
deployment.apps "my-nginx" image updated
$ kubectl describe deploy my-nginx
Name: my-nginx
…
…
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True ReplicaSetUpdated
OldReplicaSets: my-nginx-54bb7bbcf9 (3/3 replicas created)
NewReplicaSet: my-nginx-77769b7666 (1/1 replicas created) Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 27s deployment-controller Scaled up replica set my-nginx-54bb7bbcf9 to 3
Normal ScalingReplicaSet 2s deployment-controller Scaled up replica set my-nginx-77769b7666 to 1
如您所见,OldReplicaSets变为之前的ReplicaSet(my-nginx-54bb7bbcf9),而NewReplicaSet变为my-nginx-77769b7666。请注意,您可以看到OldReplicaSets属性,直到NewReplicaSet准备就绪,所以一旦新的ReplicaSet成功启动,OldReplicaSet将变为<none>,如下所示:
$ kubectl describe deploy my-nginx
Name: my-nginx
…
…
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: my-nginx-77769b7666 (3/3 replicas created)
如果您可以通过kubectl get rs看到ReplicaSet列表,则可以看到两个ReplicaSet,如下所示:
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
my-nginx-54bb7bbcf9 0 0 0 3m
my-nginx-77769b7666 3 3 3 3m
如您所见,在旧的ReplicaSet(my-nginx-54bb7bbcf9)中,DESIRED/CURRENT/READY的 Pod 数都是零。
此外,由于前面的示例使用了--record选项,您可以使用kubectl rollout history命令查看 Deployment my-nginx的部署历史,如下所示:
$ kubectl rollout history deployment my-nginx
deployments "my-nginx"
REVISION CHANGE-CAUSE
1 kubectl create --filename=deploy.yaml --save-config=true --record=true
2 kubectl set image deployment/my-nginx my-nginx=nginx:1.12.0 --record=true
更新 YAML 并使用 kubectl apply
出于演示目的,将deploy.yaml复制到deploy_1.12.2.yaml并将nginx版本更改为1.12.2,如下所示:
image: nginx:1.12.2
然后运行带有--record选项的kubectl apply命令:
$ kubectl apply -f deploy_1.12.2.yaml --record
deployment.apps "my-nginx" configured
这将执行与kubectl set镜像命令相同的操作,因此您可以看到 nginx 镜像版本已升级到1.12.2;此外,OldReplicaSets/NewReplicaSet的组合已更改如下:
$ kubectl describe deploy my-nginx
Name: my-nginx
…
…
Pod Template:
Labels: run=my-nginx
Containers:
my-nginx:
Image: nginx:1.12.2
...
...
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True ReplicaSetUpdated
OldReplicaSets: my-nginx-77769b7666 (3/3 replicas created)
NewReplicaSet: my-nginx-69fbc98fd4 (1/1 replicas created)
几秒钟后,NewReplicaSet将准备就绪。然后系统中将总共有三个ReplicaSets:
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
my-nginx-54bb7bbcf9 0 0 0 7m
my-nginx-69fbc98fd4 3 3 3 1m
my-nginx-77769b7666 0 0 0 6m
您还可以查看部署历史:
$ kubectl rollout history deployment my-nginx
deployments "my-nginx"
REVISION CHANGE-CAUSE
1 kubectl create --filename=deploy.yaml --save-config=true --record=true
2 kubectl set image deployment/my-nginx my-nginx=nginx:1.12.0 --record=true
3 kubectl apply --filename=deploy_1.12.2.yaml --record=true
每当您想要回滚到之前的ReplicaSet,即回到之前的 nginx 版本时,可以使用带有--to-revision选项的kubectl rollout undo。例如,如果您想回滚到历史记录中的修订版 2(kubectl set image deployment/my-nginx my-nginx=nginx:1.12.0 --record=true),可以指定--to-revision=2:
$ kubectl rollout undo deployment my-nginx --to-revision=2
deployment.apps "my-nginx" rolled back'
几秒钟后,Deployment 将停用当前使用nginx版本1.12.2的 Pod 模板的ReplicaSet,然后激活使用nginx版本1.12的ReplicaSet,如下所示:
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
my-nginx-54bb7bbcf9 0 0 0 8m
my-nginx-69fbc98fd4 0 0 0 2m
my-nginx-77769b7666 3 3 3 7m
另请参见
在本节中,您了解了 Deployment 的概念。它是 Kubernetes ReplicaSet 生命周期管理中的一个重要核心功能。它允许我们实现部署和回滚功能,并能集成到 CI/CD 中。在接下来的章节中,您将看到部署和回滚的详细操作:
-
在第三章中,更新实时容器部分,与容器互动
-
在第五章中,设置持续交付流水线部分,构建持续交付流水线
与服务合作
网络服务是一个接收请求并提供解决方案的应用程序。客户端通过网络连接访问服务。它们不需要了解服务的架构或其运行方式。客户端唯一需要验证的是服务的端点是否可访问,然后根据其使用策略获取服务器的响应。Kubernetes 服务具有类似的理念。并不需要在使用服务功能之前了解每个 Pod。对于 Kubernetes 系统之外的组件,它们只需通过暴露的网络端口访问 Kubernetes 服务,与运行中的 Pod 进行通信。无需了解容器的 IP 地址和端口。在 Kubernetes 服务的支持下,我们可以实现零停机时间更新容器程序,而无需费力:

Kubernetes 服务通过 Pod 的标签及其选择器覆盖 Pod
上图展示了服务的基本结构,并实现了以下概念:
-
与 Deployment 一样,服务将请求引导到具有包含服务选择器的标签的 Pod。换句话说,服务选择的 Pod 是基于其标签的。
-
发送到服务的请求负载将分配给三个 Pod。
-
Deployment 和 ReplicaSet 确保运行中的 Pod 数量符合期望状态。它监控为服务提供服务的 Pod,确保它们在接管服务职责时健康可用。
-
服务是一个抽象层,用于对 Pod 进行分组,允许 Pod 在不同节点之间进行扩展。
在本教程中,您将学习如何为 Pod 前端创建服务以处理请求。
准备工作
在应用 Kubernetes 服务之前,重要的是验证系统中所有节点是否都在运行 kube-proxy。守护进程 kube-proxy 作为节点中的网络代理工作。它帮助反映服务设置,例如 IP 或端口,并执行网络转发。要检查 kube-proxy 是否正在运行,可以查看网络连接:
// check by command netstat with proper tags for showing the information we need, t:tcp, u:udp, l:listening, p:program, n:numeric address
// use root privilege for grabbing all processes
$ sudo netstat -tulpn | grep kube-proxy
tcp 0 0 127.0.0.1:10249 0.0.0.0:* LISTEN 2326/kube-proxy
tcp6 0 0 :::31723 :::* LISTEN 2326/kube-proxy
tcp6 0 0 :::10256 :::* LISTEN 2326/kube-proxy
一旦看到输出,进程 ID 为 2326 的 kube-proxy 正在本地主机的端口 10249 上监听,那么节点就已经准备好支持 Kubernetes 服务。继续验证 Kubernetes 集群中所有节点是否都在运行 kube-proxy。
如何操作...
如前所述,Kubernetes 服务通过选择对应的标签来暴露 Pod。然而,还有一个配置需要我们关注:网络端口。如以下图所示,服务和 Pod 有各自的键值对标签和端口:

服务和 Pod 之间的网络端口映射
因此,在创建 Service 时,必须设置 Service 的选择器,并将 Service 的暴露端口绑定到容器端口。如果其中任何一项设置不当,客户端将无法获得响应,或者会收到连接拒绝的错误。
我们可以通过 CLI 或配置文件定义并创建新的 Kubernetes Service。这里,我们将解释如何通过命令部署这些 Services。以下命令使用了子命令 expose 和 describe 来处理不同的场景。对于文件格式的创建,建议阅读第三章 操作配置文件,该章节会详细讨论相关内容。
为不同资源创建 Service
你可以将一个 Service 附加到 Pod、Deployment、Kubernetes 系统外部的一个端点,甚至另一个 Service。在本节中,我们将依次展示这些操作。Kubernetes Service 的创建类似于以下命令格式:kubectl expose $RESOURCE_TYPE $RESOURCE_NAME [OTHER TAGS] 或 kubectl expose -f $CONFIG_FILE。子命令 expose 支持资源类型(Pod、Deployment 和 Service)。配置文件也被支持,并且符合类型限制。因此,在后续的演示中,我们将通过配置文件将新创建的 Service 附加到端点。
为 Pod 创建 Service
Kubernetes Pods 需要标签才能被 Service 识别,以便 Service 知道应该管理哪个 Pod。在以下命令中,我们首先创建一个带标签的 Pod,并将一个 Service 附加到该 Pod 上:
// using subcommand "run" with "never" restart policy, and without replica, you can get a Pod
// here we create a nginx container with port 80 exposed to outside world of Pod
$ kubectl run nginx-pod --image=nginx --port=80 --restart="Never" --labels="project=My-Happy-Web,role=frontend,env=test"
pod "nginx-pod" created
// expose Pod "nginx-pod" with a Service officially with port 8080, target port would be the exposed port of pod
$ kubectl expose pod nginx-pod --port=8080 --target-port=80 --name="nginx-service"
service "nginx-service" exposed
你可能会发现,根据之前的命令,我们并没有为这个 Service 指定任何选择器。然而,由于 Service nginx-service 承担了 Pod nginx-pod 的端口转发任务,它会将 Pod 的标签作为自己的选择器。请继续使用子命令 describe 查看 Service 的详细信息:
// "svc" is the abbreviate of Service, for the description's resource type
$ kubectl describe svc nginx-service
Name: nginx-service
Namespace: default
Labels: env=test
project=My-Happy-Web
role=frontend
Annotations: <none>
Selector: env=test,project=My-Happy-Web,role=frontend
Type: ClusterIP
IP: 10.96.107.213
Port: <unset> 8080/TCP
TargetPort: 80/TCP
Endpoints: 192.168.79.24:80
Session Affinity: None
Events: <none>
现在你可以看到,为了保证职责明确,成功暴露的 Service 将 Pod 的标签复制为自己的选择器。Endpoints 后面的值列表是 Pod 的 IP 地址和它暴露的端口 80。此外,Service 将 Pod 的标签作为自己的标签。根据这个例子,Pod 可以通过 Service 访问,URL 为 10.96.107.213:8080。
除了 Service 的选择器外,如果用户跳过某些参数,它们可以自动配置。一个参数是 Pod 的标签;另一个是 Service 的名称;还有一个是 Service 的暴露端口。我们来看一下如何管理这组简单的 Pod 和 Service:
// create a Pod and a Service for it
$ kubectl run nginx-no-label --image=nginx --port=80 --restart="Never" && kubectl expose pod nginx-no-label
pod "nginx-no-label" created
service "nginx-no-label" exposed
// take a lookat the configurations of the Service
$ kubectl describe svc nginx-no-label
Name: nginx-no-label
Namespace: default
Labels: run=nginx-no-label
Annotations: <none>
Selector: run=nginx-no-label Type: ClusterIP
IP: 10.105.96.243
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: 192.168.79.10:80
Session Affinity: None
Events: <none>
在这里,我们可以看到 Service 从 Pod 继承了名称、标签和端口。选择器被分配了一个虚拟标签,键名为 run,值为 Pod 的名称,这里是 Pod nginx-no-label 的虚拟名称。用户也应通过端口 80 访问该 Service。对于如此简单的设置,您还可以尝试以下命令,同时创建 Pod 和 Service:
// through leveraging tag "--expose", create the Service along with Pod
$ kubectl run another-nginx-no-label --image=nginx --port=80 --restart="Never" --expose
service "another-nginx-no-label" created
pod "another-nginx-no-label" created
为 Deployment 创建带有外部 IP 的 Service
Kubernetes Deployment 是 Service 的理想资源类型。对于由 ReplicaSet 和 Deployment 监控的 Pods,Kubernetes 系统有一个控制器管理器来管理它们的生命周期。它也有助于通过将现有的 Services 绑定到另一个 Deployment 来更新程序的版本或状态。对于以下命令,我们首先创建一个 Deployment,然后附加一个具有外部 IP 的 Service:
// using subcommand "run" and assign 2 replicas
$ kubectl run nginx-deployment --image=nginx --port=80 --replicas=2 --labels="env=dev,project=My-Happy-Web,role=frontend"
deployment.apps "nginx-deployment" created
// explicitly indicate the selector of Service by tag "--selector", and assign the Service an external IP by tag "--external-ip"
// the IP 192.168.122.102 demonstrated here is the IP of one of the Kubernetes node in system
$ kubectl expose deployment nginx-deployment --port=8080 --target-port=80 --name="another-nginx-service" --selector="project=My-Happy-Web,role=frontend" --external-ip="192.168.122.102"
service "another-nginx-service" exposed
让我们继续查看新创建的 Service another-nginx-service 的详细信息:
$ kubectl describe svc another-nginx-service
Name: another-nginx-service
Namespace: default
Labels: env=dev
project=My-Happy-Web
role=frontend
Annotations: <none>
Selector: project=My-Happy-Web,role=frontend
Type: ClusterIP
IP: 10.100.109.230
External IPs: 192.168.122.102
Port: <unset> 8080/TCP
TargetPort: 80/TCP
Endpoints: 192.168.79.15:80,192.168.79.21:80,192.168.79.24:80
Session Affinity: None
Events: <none>
除了 Service IP(在前面的命令中是 10.100.109.230),它可以在 Kubernetes 系统内访问之外,Service 现在还可以通过 Kubernetes 系统外的外部 IP 连接(例如 192.168.122.102)。虽然 Kubernetes 主节点能够与每个节点通信,但在这种情况下,我们可以向 Service 发起如下命令请求:
$ curl 192.168.122.102:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
为没有选择器的 Endpoint 创建 Service
首先,我们将创建一个指向外部服务的 Endpoint。Kubernetes Endpoint 是一种抽象,将 Kubernetes 以外的组件(例如,其他系统中的数据库)纳入 Kubernetes 资源的一部分。它为混合环境提供了可行的使用案例。要创建一个 Endpoint,需要提供 IP 地址和端口。请查看以下模板:
$ cat k8s-endpoint.yaml
apiVersion: v1
kind: Endpoints
metadata:
name: k8s-ep
subsets:
- addresses:
- hostname: kubernetes-io
ip: 45.54.44.100
ports:
- port: 80
模板定义了一个名为 k8s-ep 的 Endpoint,它指向官方 Kubernetes 网站主机的 IP(kubernetes.io)。无需在意这个 Endpoint 转发到的是普通的 HTML 页面;我们只是把这个 Endpoint 作为示例。如前所述,Endpoint 不是 Kubernetes API 支持的暴露资源:
// Give it a try!
$ kubectl expose -f k8s-endpoint.yaml
error: cannot expose a { Endpoints}
在 Kubernetes 中,Endpoint 不仅代表外部服务;内部的 Kubernetes Service 也是一个 Kubernetes Endpoint。您可以通过命令 kubectl get endpoints 检查 Endpoint 资源。您会发现,除了刚刚创建的名为 k8s-ep 的 Endpoint 外,还有许多与前面页面中 Service 同名的 Endpoint。当 Service 使用选择器并暴露某些资源(如 Pod、Deployment 或其他 Service)时,会同时创建一个同名的 Endpoint。
因此,我们仍然可以使用相同名称创建与 Endpoint 关联的 Service,如以下模板所示:
$ cat endpoint-service.yaml
apiVersion: v1
kind: Service
metadata:
name: k8s-ep
spec:
ports:
- protocol: TCP
port: 8080
targetPort: 80
端点(Endpoints)与服务(Service)之间的关系是通过资源名称建立的。对于服务k8s-ep,我们没有指定选择器,因为它实际上并没有负责任何 Pod:
// go create the Service and the endpoint
$ kubectl create -f endpoint-service.yaml && kubectl create -f k8s-endpoint.yaml
service "k8s-ep" created
endpoints "k8s-ep" created
// verify the Service k8s-ep
$ kubectl describe svc k8s-ep
Name: k8s-ep
Namespace: default
Labels: <none>
Annotations: <none>
Selector: <none>
Type: ClusterIP
IP: 10.105.232.226
Port: <unset> 8080/TCP
TargetPort: 80/TCP
Endpoints: 45.54.44.100:80
Session Affinity: None
Events: <none>
现在你可以看到,服务的端点就是在k8s-endpoint.yaml中定义的那个。通过 Kubernetes 服务访问外部世界对我们来说是很方便的!在之前的例子中,我们可以通过以下命令验证结果:
$ curl 10.105.232.226:8080
为另一个具有会话亲和性的服务创建服务
在构建一个服务时,我们可能会考虑多层端口转发。尽管是将流量从一个端口重定向到另一个端口,但暴露一个服务的操作实际上是在将一个服务的设置复制到另一个服务中。这个场景可以用作更新服务设置,而不会给当前客户端和服务器带来麻烦:
// create a Service by expose an existed one
// take the one we created for Deployment for example
$ kubectl expose svc another-nginx-service --port=8081 --target-port=80 --name=yet-another-nginx-service --session-affinity="ClientIP"
service "yet-another-nginx-service" exposed
// check the newly created Service
$ kubectl describe svc yet-another-nginx-service
Name: yet-another-nginx-service
Namespace: default
Labels: env=dev
project=My-Happy-Web
role=frontend
Annotations: <none>
Selector: project=My-Happy-Web,role=frontend
Type: ClusterIP
IP: 10.110.218.136
Port: <unset> 8081/TCP
TargetPort: 80/TCP
Endpoints: 192.168.79.15:80,192.168.79.21:80,192.168.79.24:80
Session Affinity: ClientIP
Events: <none>
这里就是!我们成功地暴露了另一个具有类似设置的服务,名为another-nginx-service。命令和输出可以总结如下:
-
需要新的服务名称:虽然我们可以从另一个服务复制配置,但资源类型的名称应该始终是唯一的。当暴露一个没有
--name标签的服务时,你会收到错误消息:Error from server (AlreadyExists): services "another-nginx-service" already exists。 -
添加或更新配置是可行的:我们可以添加新的配置,比如添加会话亲和性;或者我们可以更新服务的端口,例如这里,我们将端口从
8080更改为8081。 -
避免更改目标端口:因为目标端口是与 Pods 的 IP 一起使用的,一旦暴露的服务更改了目标端口,新复制的服务将无法将流量转发到相同的端点。在前面的示例中,由于定义了新的目标端口,我们应该再次指出容器端口。这避免了新服务使用目标端口作为容器端口,避免了产生误导性的交易。
使用会话亲和性时,描述标签列表中的会话亲和性为ClientIP。对于当前的 Kubernetes 版本,客户端 IP 是唯一支持的会话亲和性选项。它的行为像哈希函数一样:使用相同的 IP 地址,请求将始终发送到相同的 Pod。然而,如果 Kubernetes 服务前面有负载均衡器或入口控制器,这可能会成为问题:请求会被视为来自相同的源,流量会被转发到单一的 Pod。用户必须自己处理这个问题,例如,通过建立一个 HA 代理服务器,而不是使用 Kubernetes 服务。
删除服务
如果您浏览本节中的每个命令,肯定会看到应该删除的一些演示的 Kubernetes 服务(我们统计了六个)。要删除服务,与任何其他 Kubernetes 资源一样,可以通过子命令delete删除带有名称或配置文件的服务。当您尝试同时删除服务和端点时,将发生以下情况:
// the resource abbreviation of endpoint is "ep", separate different resource types by comma
$ kubectl delete svc,ep k8s-ep
service "k8s-ep" deleted
Error from server (NotFound): endpoints "k8s-ep" not found
这是因为 Service 也是 Kubernetes 的 Endpoint。这就是为什么,尽管我们分别创建了 Service 和端点,但一旦它们被视为作为一个单元工作,删除 Service 时会删除 Endpoint。因此,错误消息表明没有名为k8s-ep的端点,因为它已随 Service 删除。
工作原理...
在网络协议栈上,Kubernetes 服务依赖于传输层,与覆盖网络和kube-proxy一起工作。Kubernetes 的覆盖网络通过分配子网租约从预配置的地址空间中建立集群网络,并将网络配置存储在etcd中;另一方面,kube-proxy通过iptables设置帮助将来自服务端点的流量转发到 Pod。
代理模式和 Service kube-proxy 目前有三种具有不同实现方法的模式:userspace、iptables和ipvs。这些模式影响客户端请求如何通过 Kubernetes 服务到达特定的 Pod:
-
userspace:kube-proxy为每个本地节点上的 Service 打开一个随机端口,称为代理端口,然后更新iptables规则,捕获发送到 Service 的任何请求并转发到代理端口。最终,发送到代理端口的任何消息都将传递给 Service 覆盖的 Pod。由于流量需要经过kube-proxy路由到 Pod,效率较低。 -
iptables:与userspace模式一样,重定向客户端流量也需要iptables规则。但是没有代理端口作为中介。更快但需要注意 Pod 的存活性。默认情况下,如果目标 Pod 失败,请求无法重试到另一个 Pod。为了避免访问不健康的 Pod,必须对 Pod 进行健康检查并及时更新iptables。 -
ipvs:ipvs是 Kubernetes v1.9 版本中的 Beta 功能。在这种模式下,kube-proxy在 Service 和其后端集之间建立名为 netlink 的接口。ipvs模式处理了userspace和iptables的缺点;它甚至更快,因为路由规则存储在内核空间的哈希表结构中,kube-proxy持续检查netlinks的一致性。ipvs甚至提供多种负载平衡选项。
系统选择最佳和稳定的作为kube-proxy的默认设置。目前,默认设置是iptables模式。
当一个 Pod 尝试与服务进行通信时,它可以通过环境变量或 DNS 主机查找来找到服务。让我们在下面的示例中尝试访问 Pod 中的服务:
// run a Pod first, and ask it to be alive 600 seconds
$ kubectl run my-1st-centos --image=centos --restart=Never sleep 600
pod "my-1st-centos" created
// run a Deployment of nginx and its Service exposing port 8080 for nginx
$ kubectl run my-nginx --image=nginx --port=80
deployment.apps "my-nginx" created
$ kubectl expose deployment my-nginx --port=8080 --target-port=80 --name="my-nginx-service"
service "my-nginx-service" exposed
// run another pod
$ kubectl run my-2nd-centos --image=centos --restart=Never sleep 600
pod "my-2nd-centos" created
//Go check the environment variables on both pods.
$ kubectl exec my-1st-centos -- /bin/sh -c export
$ kubectl exec my-2nd-centos -- /bin/sh -c export
你会发现 Pod my-2nd-centos显示了附加的变量,显示了服务my-nginx-service的相关信息,如下所示:
export MY_NGINX_SERVICE_PORT="tcp://10.104.218.20:8080"
export MY_NGINX_SERVICE_PORT_8080_TCP="tcp://10.104.218.20:8080"
export MY_NGINX_SERVICE_PORT_8080_TCP_ADDR="10.104.218.20"
export MY_NGINX_SERVICE_PORT_8080_TCP_PORT="8080"
export MY_NGINX_SERVICE_PORT_8080_TCP_PROTO="tcp"
export MY_NGINX_SERVICE_SERVICE_HOST="10.104.218.20"
export MY_NGINX_SERVICE_SERVICE_PORT="8080"
这是因为系统未能实时更新服务;只有之后创建的 Pod 才能通过环境变量访问服务。在这种依赖顺序的约束下,如果你的 Kubernetes 资源必须以这种方式相互交互,请注意按正确的顺序运行它们。表示服务主机的环境变量键将形成<SERVICE NAME>_SERVICE_HOST,而服务端口则类似<SERVICE NAME>_SERVICE_PORT。在前面的示例中,名称中的破折号也被转换为下划线:
// For my-2nd-centos, getting information of Service by environment variables
$ kubectl exec my-2nd-centos -- /bin/sh -c 'curl $MY_NGINX_SERVICE_SERVICE_HOST:$MY_NGINX_SERVICE_SERVICE_PORT'
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
然而,如果已安装kube-dns插件,作为 Kubernetes 系统中的 DNS 服务器,任何在相同命名空间中的 Pod 都可以访问服务,无论该服务何时创建。服务的主机名将形成<SERVICE NAME>.<NAMESPACE>.svc.cluster.local的形式。cluster.local是启动kube-dns时定义的默认集群域:
// go accessing my-nginx-service by A record provided by kube-dns
$ kubectl exec my-1st-centos -- /bin/sh -c 'curl my-nginx-service.default.svc.cluster.local:8080'
$ kubectl exec my-2nd-centos -- /bin/sh -c 'curl my-nginx-service.default.svc.cluster.local:8080'
还有更多内容...
Kubernetes 服务有四种类型:ClusterIP、NodePort、LoadBalancer和ExternalName。在本食谱的如何操作...部分,我们仅展示默认类型ClusterIP。ClusterIP类型表示 Kubernetes 服务在覆盖网络中被分配了一个唯一的虚拟 IP,这也意味着该服务在此 Kubernetes 集群中的身份。ClusterIP保证该服务在内部是可访问的。
以下图表表达了各类型的可用性覆盖范围及其入口点:

四种服务类型及其入口点
对于NodePort类型,它包含了ClusterIP的所有特性,具有一个对等访问的虚拟 IP,并且允许用户在每个节点上通过相同的端口暴露服务。LoadBalancer类型位于其他两种类型之上。LoadBalancer服务会在内部和节点上都暴露。更重要的是,如果你的云服务商支持外部负载均衡服务器,你可以将负载均衡器 IP 绑定到服务上,这将成为另一个暴露点。另一方面,ExternalName类型用于指向 Kubernetes 系统外部的端点。它类似于我们在前一部分使用配置文件创建的端点;此外,单个ExternalName服务可以提供此功能。
我们可以使用子命令create来创建不同类型的服务:
// create a NodePort Service
// the tag "tcp" is for indicating port configuration: SERVICE_PORT:TARGET_PORT
$ kubectl create service nodeport my-nginx --tcp=8080:80
service "my-nginx" created
$ kubectl describe svc my-nginx
Name: my-nginx
Namespace: default
Labels: app=my-nginx
Annotations: <none>
Selector: app=my-nginx
Type: NodePort
IP: 10.105.106.134
Port: 8080-80 8080/TCP
TargetPort: 80/TCP
NodePort: 8080-80 31336/TCP
Endpoints: <none>
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
在此NodePort类型服务的示例中,你可以看到它仍然具有虚拟 IP(10.105.106.134),并且可以通过任何 Kubernetes 节点的31336端口进行访问:
// run an nginx Deployment with the label as NodePort Service my-nginx's selector
$ kubectl run test-nodeport --image=nginx --labels="app=my-nginx"
deployment.apps "test-nodeport" created
// check the Kubernetes node with Service port on the node
$ curl ubuntu02:31336
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
在这里,我们演示了如何创建一个ExternalName服务,该服务暴露了CNAME kubernetes.io:
$ kubectl create service externalname k8s-website --external-name kubernetes.io
service "k8s-website" created
// create a CentOS Pod for testing the Service availability
$ kubectl run my-centos --image=centos --restart=Never sleep 600
pod "my-centos" created
//now you can check the Service by Service's DNS name
$ kubectl exec -it my-centos -- /bin/sh -c 'curl k8s-website.default.svc.cluster.local '
//Check all the Services we created in this section
//ExternalName Service has no cluster IP as defined
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
k8s-website ExternalName <none> kubernetes.io <none> 31m
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 14d
my-nginx NodePort 10.105.106.134 <none> 8080:31336/TCP 1h
然而,我们无法通过子命令expose在 CLI 中创建ExternalName服务,因为expose是用来暴露 Kubernetes 资源的,而ExternalName服务是用于外部世界的资源。因此,ExternalName服务无需定义选择器也是合理的。
使用子命令create创建服务 在使用子命令create创建服务时,命令行将是这样的:kubectl create service <SERVICE TYPE> <SERVICE NAME> [OPTIONS]。我们可以在<SERVICE TYPE>中指定服务类型,例如clusterip、nodeport、loadbalancer和externalname。使用这种方法时,我们不能指定服务的选择器。正如我们在这一部分中创建的NodePort服务一样,它只会创建一个默认选择器app: my-nginx,我们必须将此标签分配给后续创建的部署test-nodeport。除了ExternalName类型,其他服务类型可以使用子命令expose并通过type标签创建。尝试使用kubectl expose为现有资源创建NodePort服务!
另见
为了获得 Kubernetes 服务的最佳实践,建议阅读第二章中的以下食谱,深入了解 Kubernetes 概念:
-
部署 API
-
使用 Secrets
-
使用标签和选择器
还有更多高级知识可以使你的服务更具功能性和灵活性,敬请关注:
-
第三章中 转发容器端口 部分,玩转容器
-
第三章中 确保容器灵活使用 部分,玩转容器
使用卷
容器中的文件是短暂的。当容器被终止时,文件也会消失。Docker 引入了数据卷来帮助我们持久化数据(docs.docker.com/engine/admin/volumes/volumes)。然而,当涉及到多个主机时,作为容器集群,要管理所有容器和主机之间的卷以实现文件共享或动态提供卷就变得很困难。Kubernetes 引入了卷,它随着 Pod 的生命周期而存在。它支持多种类型的卷,包括流行的网络磁盘解决方案和不同公共云中的存储服务。以下是一些:
| 卷类型 | 存储提供商 |
|---|---|
emptyDir |
本地主机 |
hostPath |
本地主机 |
glusterfs |
GlusterFS 集群 |
downwardAPI |
Kubernetes Pod 信息 |
nfs |
NFS 服务器 |
awsElasticBlockStore |
亚马逊 Web 服务 Amazon Elastic Block Store |
gcePersistentDisk |
Google Compute Engine 持久磁盘 |
azureDisk |
Azure 磁盘存储 |
projected |
Kubernetes 资源;当前支持secret、downwardAPI和configMap |
secret |
Kubernetes Secret 资源 |
vSphereVolume |
vSphere VMDK 卷 |
gitRepo |
Git 仓库 |
准备就绪
在使用 Kubernetes 中的卷时,需要存储提供程序,除了emptyDir,当 Pod 被删除时,它将被清除。对于其他存储提供程序,在 Pod 定义之前必须建立文件夹、服务器或集群。动态配置在 Kubernetes 版本 1.6 中提升为稳定版,允许根据支持的云提供商来配置存储。
在这一部分,我们将详细介绍emptyDir、hostPath、nfs、glusterfs、downwardAPI和gitRepo的细节。下一节将介绍用于存储凭据的Secret。另一方面,Projected是一种将其他卷资源分组到单个挂载点下的方法。由于它仅支持secret、downwardAPI和configMap,我们将在Secret部分介绍它。其余的卷类型具有类似的 Kubernetes 语法,只是具有不同的后端卷实现。
如何做...
卷在 Pod 定义的卷部分中使用唯一名称进行定义。每种类型的卷都有不同的配置需要设置。一旦定义了卷,可以在容器规范的volumeMounts部分中挂载它们。需要volumeMounts.name和volumeMounts.mountPath,分别表示您定义的卷的名称和容器内部的挂载路径。
我们将使用 YAML 格式的 Kubernetes 配置文件来创建带有卷的 Pod,如下例所示。
emptyDir
emptyDir是最简单的卷类型,它将为同一 Pod 中的容器创建一个空卷。当删除 Pod 时,emptyDir中的文件也将被删除。emptyDir在创建 Pod 时创建。在以下配置文件中,我们将创建一个运行 Ubuntu 的 Pod,并使用命令休眠3600秒。如您所见,在卷部分定义了一个名为 data 的卷,并且卷将在 Ubuntu 容器中的/data-mount路径下挂载:
// configuration file of emptyDir volume
# cat 2-6-1_emptyDir.yaml
apiVersion: v1
kind: Pod
metadata:
name: ubuntu
labels:
name: ubuntu
spec:
containers:
- image: ubuntu
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
name: ubuntu
volumeMounts:
- mountPath: /data-mount
name: data
volumes:
- name: data
emptyDir: {}
// create pod by configuration file emptyDir.yaml
# kubectl create -f 2-6-1_emptyDir.yaml
pod "ubuntu" created
检查 Pod 运行在哪个节点上 使用kubectl describe pod <Pod name> | grep Node命令,您可以检查 Pod 运行在哪个节点上。
Pod 运行后,您可以在目标节点上使用docker inspect <container ID>,您可以看到容器内部的详细挂载点:
"Mounts": [
...
{
"Type": "bind",
"Source": "/var/lib/kubelet/pods/98c7c676-e9bd-11e7-9e8d-080027ac331c/volumes/kubernetes.io~empty-dir/data",
"Destination": "/data-mount",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
...
]
Kubernetes 将/var/lib/kubelet/pods/<id>/volumes/kubernetes.io~empty-dir/<volumeMount name>挂载到 Pod 使用的/data-mount。如果创建一个具有多个容器的 Pod,它们都将以相同的源挂载到相同的目标/data-mount。默认的挂载传播方式是rprivate,这意味着主机上的任何挂载点在容器中是不可见的,反之亦然。
emptyDir 可以通过将 emptyDir.medium 设置为 Memory 来挂载为 tmpfs。
以之前的配置文件 2-6-1_emptyDir_mem.yaml 为例,内容如下:
volumes:
-
name: data
emptyDir:
medium: Memory
我们可以通过 kubectl exec <pod_name> <commands> 命令来验证是否成功挂载。在容器中运行 df 命令:
# kubectl exec ubuntu df
Filesystem 1K-blocks Used Available Use% Mounted on
...
tmpfs 1024036 0 1024036 0% /data-mount
...
请注意,tmpfs 存储在内存中,而不是文件系统中。不会创建文件,并且在每次重启时会被清空。此外,它受 Kubernetes 中内存限制的约束。有关容器资源限制的更多信息,请参见本章中的 Working with Namespace。
如果 Pod 内有多个容器,则 Kubectl exec 命令为 kubectl exec <pod_name> <container_name> <commands>。
hostPath
hostPath 在 Docker 中充当数据卷。列在 hostPath 中的节点本地文件夹将被挂载到 Pod 中。由于 Pod 可以在任何节点上运行,因此在卷中发生的读写操作显式存在于 Pod 所运行的节点上。然而,在 Kubernetes 中,Pod 不应意识到节点的存在。请注意,使用 hostPath 时,配置和文件在不同节点上可能会有所不同。因此,通过相同的命令或配置文件创建的相同 Pod 在不同节点上可能会表现得不同。
通过使用 hostPath,您可以在容器和节点本地磁盘之间读取和写入文件。我们在定义卷时需要通过 hostPath.path 来指定节点上目标挂载文件夹:
apiVersion: v1
# cat 2-6-2_hostPath.yaml
kind: Pod
metadata:
name: ubuntu
spec:
containers:
-
image: ubuntu
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
name: ubuntu
volumeMounts:
-
mountPath: /data-mount
name: data
volumes:
-
name: data
hostPath:
path: /tmp/data
使用 docker inspect 查看卷的详细信息,您将看到主机上的卷被挂载到 /data-mount 目录:
"Mounts": [
{
"Type": "bind",
"Source": "/tmp/data",
"Destination": "/data-mount",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
},
...
]
如果我们运行 kubectl exec ubuntu touch /data-mount/sample,我们应该能在主机的 /tmp/data 目录下看到一个名为 sample 的空文件。
NFS
您可以将 网络文件系统 (NFS) 挂载到 Pod 上,作为 nfs volume。多个 Pod 可以挂载并共享同一个 nfs volume 中的文件。存储到 nfs volume 中的数据将在 Pod 生命周期中持久存在。在使用 nfs volume 之前,您需要创建自己的 NFS 服务器,并确保 Kubernetes 节点上安装了 nfs-utils 包。
在离开之前,请检查您的 NFS 服务器是否正常工作。您应该检查 /etc/exports 文件,并确保正确的共享参数和目录设置,使用 mount -t nfs <nfs server>:<share name> <local mounted point> 命令检查是否可以在本地挂载。
带有 NFS 的卷类型配置文件与其他配置类似,但在卷定义中需要指定 nfs.server 和 nfs.path,以指定 NFS 服务器信息和挂载路径。nfs.readOnly 是一个可选字段,用于指定卷是否为只读(默认为 false):
# configuration file of nfs volume
$ cat 2-6-3_nfs.yaml
apiVersion: v1
kind: Pod
metadata:
name: nfs
spec:
containers:
-
name: nfs
image: ubuntu
volumeMounts:
- name: nfs
mountPath: "/data-mount"
volumes:
- name: nfs
nfs:
server: <your nfs server>
path: "/"
在运行kubectl create –f 2-6-3_nfs.yaml后,你可以通过kubectl describe <pod name>来描述你的 Pod,检查挂载状态。如果挂载成功,应该会显示条件,Ready 为 true,并显示你挂载的目标nfs:
Conditions:
Type Status
Ready True
Volumes:
nfs:
Type: NFS (an NFS mount that lasts the lifetime of a pod)
Server: <your nfs server>
Path: /
ReadOnly: false
如果我们使用docker命令检查容器,我们可以在Mounts部分看到卷信息:
"Mounts": [
{
"Source": "/var/lib/kubelet/pods/<id>/volumes/kubernetes.io~nfs/nfs",
"Destination": "/data-mount",
"Mode": "",
"RW": true
},
...
]
实际上,Kubernetes 会将你的<nfs server>:<share name>挂载到/var/lib/kubelet/pods/<id>/volumes/kubernetes.io~nfs/nfs,然后将其作为目标挂载到容器的/data-mount中。你也可以使用kubectl exec来触摸文件,测试它是否已经正确挂载。
glusterfs
GlusterFS (www.gluster.org) 是一个可扩展的网络附加存储文件系统。glusterfs卷类型允许你将 GlusterFS 卷挂载到 Pod 中。就像 NFS 卷一样,glusterfs卷中的数据在 Pod 生命周期内是持久的。如果 Pod 终止,数据仍然可以在glusterfs卷中访问。在使用glusterfs卷之前,你应该先搭建好 GlusterFS 系统。
在继续之前,检查glusterfs是否有效。通过使用glusterfs卷信息,可以查看 GlusterFS 服务器上当前可用的卷。通过在本地使用mount -t glusterfs <glusterfs server>:/<volume name> <local mounted point>,你可以检查 GlusterFS 系统是否能够成功挂载。
由于 GlusterFS 中的卷副本必须大于1,假设我们在gfs1和gfs2服务器上有两个副本,卷的名称是gvol。
首先,我们需要创建一个端点,作为gfs1和gfs2的桥接:
$ cat 2-6-4_gfs-endpoint.yaml
kind: Endpoints
apiVersion: v1
metadata:
name: glusterfs-cluster
subsets:
-
addresses:
-
ip: <gfs1 server ip>
ports:
-
port: 1
-
addresses:
-
ip: <gfs2 server ip>
ports:
-
port: 1
# create endpoints
$ kubectl create –f 2-6-4_gfs-endpoint.yaml
然后,我们可以使用kubectl get endpoints检查端点是否正确创建:
$kubectl get endpoints
NAME ENDPOINTS AGE
glusterfs-cluster <gfs1>:1,<gfs2>:1 12m
接下来,我们应该能够通过glusterfs.yaml创建一个带有glusterfs卷的 Pod。glusterfs卷定义的参数包括glusterfs.endpoints,它指定我们刚才创建的端点名称,以及glusterfs.path,它是卷的名称gvol。glusterfs.readOnly用于设置卷是否以只读模式挂载:
$ cat 2-6-4_glusterfs.yaml
apiVersion: v1
kind: Pod
metadata:
name: ubuntu
spec:
containers:
-
image: ubuntu
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
name: ubuntu
volumeMounts:
-
mountPath: /data-mount
name: data
volumes:
-
name: data
glusterfs:
endpoints: glusterfs-cluster
path: gvol
让我们使用kubectl describle检查卷设置:
Volumes:
data:
Type: Glusterfs (a Glusterfs mount on the host that shares a pod's lifetime)
EndpointsName: glusterfs-cluster
Path: gvol
ReadOnly: false
使用docker inspect,你应该能够看到挂载源是/var/lib/kubelet/pods/<id>/volumes/kubernetes.io~glusterfs/data,目标是/data-mount。
downwardAPI
downwardAPI卷用于将 Pod 信息暴露到容器中。downwardAPI的定义是一个项目列表。一个项目包含一个路径和fieldRef。Kubernetes 会将fieldRef中列出的指定元数据转储到mountPath下名为path的文件中,并将<volume name>挂载到你指定的目标。当前支持的downwardAPI卷元数据包括:
| 字段路径 | 作用域 | 定义 |
|---|---|---|
spec.nodeName |
Pod | Pod 运行的节点 |
spec.serviceAccountName |
Pod | 与当前 Pod 关联的服务账户 |
metadata.name |
Pod | Pod 的名称 |
metadata.namespace |
Pod | Pod 所属的命名空间 |
metadata.annotations |
Pod | Pod 的注解 |
metadata.labels |
Pod | Pod 的标签 |
status.podIP |
Pod | Pod 的 IP |
limits.cpu |
Container | 容器的 CPU 限制 |
requests.cpu |
Container | 容器的 CPU 请求 |
limits.memory |
Container | 容器的内存限制 |
requests.memory |
Container | 容器的内存请求 |
limits.ephemeral-storage |
Container | 容器的临时存储限制 |
requests.ephemeral-storage |
Container | 容器的临时存储请求 |
如果作用域是 Pod,我们使用fieldRef.fieldPath;如果作用域是容器,则使用resourceFieldRef。例如,以下配置文件可以在 Ubuntu 容器中的/data-mount卷中暴露metadata.labels:
// pod scope example
# cat 2-6-5_downward_api.yaml
apiVersion: v1
kind: Pod
metadata:
name: downwardapi
labels:
env: demo
spec:
containers:
-
name: downwardapi
image: ubuntu
command:
- sleep
- "3600"
volumeMounts:
- name: podinfo
mountPath: "/data-mount"
volumes:
- name: podinfo
downwardAPI:
items:
- path: metadata
fieldRef:
fieldPath: metadata.labels
通过描述pod,我们可以检查卷是否成功挂载到/data-mount,并且metadata.labels指向metadata文件:
// describe the pod
# kubectl describe pod downwardapi
...
Mounts:
/data-mount from podinfo (rw)
...
Volumes:
podinfo:
Type: DownwardAPI (a volume populated by information about the pod)
Items:
metadata.labels -> metadata
我们可以使用kubectl exec downwardapi cat /data-mount/metadata命令检查容器内部的文件,你应该能够看到env="example" presents。
如果它在容器范围内,我们需要指定容器名称:
# cat 2-6-5_downward_api_container.yaml
apiVersion: v1
kind: Pod
metadata:
name: downwardapi-container
spec:
containers:
-
name: downwardapi
image: ubuntu
command:
- sleep
- "3600"
volumeMounts:
- name: podinfo
mountPath: "/data-mount"
volumes:
- name: podinfo
downwardAPI:
items:
- path: "cpu_limit"
resourceFieldRef:
containerName: downwardapi
resource: limits.cpu
我们可以在节点内部使用docker inspect <container_name>命令来检查实现:
{
"Source": "/var/lib/kubelet/pods/<id>/volumes/kubernetes.io~downward-api/<volume name>",
"Destination": "/data-mount",
"Mode": "",
"RW": true
}
Kubernetes 在源卷中暴露pod信息,并将其挂载到/data-mount。
对于 Pod 的 IP,使用环境变量在 Pod 规格中传播会更加容易:
spec:
containers:
- name: envsample-pod-info
env:
- name: MY_POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
Kubernetes GitHub 上的示例文件夹(kubernetes.io/docs/tasks/inject-data-application/downward-api-volume-expose-pod-information)包含更多关于环境变量和downwardAPI卷的示例。
gitRepo
gitRepo是一种方便的卷类型,它将现有的 Git 仓库克隆到容器中:
// an example of how to use gitRepo volume type
# cat 2-6-6_gitRepo.yaml
apiVersion: v1
kind: Pod
metadata:
name: gitrepo
spec:
containers:
- image: ubuntu
name: ubuntu
command:
- sleep
- "3600"
volumeMounts:
- mountPath: /app
name: app-git
volumes:
- name: app-git
gitRepo:
repository: "https://github.com/kubernetes-cookbook/second-edition.git"
revision: "9d8e845e2f55a5c65da01ac4235da6d88ef6bcd0"
# kubectl create -f 2-6-6_gitRepo.yaml
pod "gitrepo" created
在前面的例子中,卷插件挂载了一个空目录并运行git clone <gitRepo.repolist>来克隆仓库。然后,Ubuntu 容器将能够访问它。
还有更多...
在前面的情况中,用户需要了解存储提供者的详细信息。Kubernetes 提供了PersistentVolumes和PersistentVolumeClaim来抽象存储提供者和存储消费者的细节。
PersistentVolumes
以下是PersistentVolume的示意图。首先,管理员提供PersistentVolume的规范。然后,消费者通过PersistentVolumeClaim请求存储。最后,Pod 通过PersistentVolumeClaim的引用挂载卷:

PersistentVolumeClaims 是一个抽象层,用于解耦 Pod 的卷和物理卷资源。
这是使用NFS的示例。管理员需要先提供并分配PersistentVolume:
# example of PV with NFS
$ cat 2-6-7_pv.yaml
apiVersion: "v1"
kind: "PersistentVolume"
metadata:
name: "pvnfs01"
spec:
capacity:
storage: "3Gi"
accessModes:
- "ReadWriteOnce"
nfs:
path: "/"
server: "<your nfs server>"
persistentVolumeReclaimPolicy: "Recycle"
# create the pv
$ kubectl create -f 2-6-7_pv.yaml
persistentvolume "pvnfs01" created
我们可以看到这里有三个参数:capacity、accessModes和persistentVolumeReclaimPolicy。capacity是此PersistentVolume的大小。现在,accessModes是基于存储提供商的能力,并且可以在提供期间设置为特定模式。例如,NFS 支持多个读写者同时访问——那么我们可以将accessModes指定为ReadWriteOnce、ReadOnlyMany或ReadWriteMany之一。现在,persistentVolumeReclaimPolicy用于定义当PersistentVolume被释放时的行为。目前支持的策略是对于nfs和hostPath的保留和回收。在保留模式下,你必须自己清理卷;另一方面,Kubernetes 会在回收模式下清理卷。
PV 是像节点一样的资源。我们可以使用kubectl get pv查看当前提供的 PV:
# list current PVs
$ kubectl get pv
NAME LABELS CAPACITY ACCESSMODES STATUS CLAIM REASON AGE
pvnfs01 <none> 3Gi RWO Bound default/pvclaim01 37m
接下来,我们需要将PersistentVolume与PersistentVolumeClaim绑定,以便将其作为卷挂载到pod中:
# example of PersistentVolumeClaim
$ cat claim.yaml
apiVersion: "v1"
kind: "PersistentVolumeClaim"
metadata:
name: "pvclaim01"
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
# create the claim
$ kubectl create -f claim.yaml
persistentvolumeclaim "pvclaim01" created
# list the PersistentVolumeClaim (pvc)
$ kubectl get pvc
NAME LABELS STATUS VOLUME CAPACITY ACCESSMODES AGE
pvclaim01 <none> Bound pvnfs01 3Gi RWO 59m
PersistentVolumeClaim中可以设置accessModes和存储的约束。如果声明成功绑定,状态将变为Bound;另一方面,如果状态为Unbound,则表示当前没有 PV 与请求匹配。
然后,我们可以通过PersistentVolumeClaim的引用将 PV 挂载为卷:
# example of mounting into Pod
$ cat nginx.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
project: pilot
environment: staging
tier: frontend
spec:
containers:
-
image: nginx
imagePullPolicy: IfNotPresent
name: nginx
volumeMounts:
- name: pv
mountPath: "/usr/share/nginx/html"
ports:
- containerPort: 80
volumes:
- name: pv
persistentVolumeClaim:
claimName: "pvclaim01"
# create the pod
$ kubectl create -f nginx.yaml
pod "nginx" created
语法将与其他卷类型类似。只需在卷定义中添加persistentVolumeClaim的claimName。一切准备就绪!让我们检查详细信息,看看是否成功挂载:
# check the details of a pod
$ kubectl describe pod nginx
...
Volumes:
pv:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: pvclaim01
ReadOnly: false
...
我们可以看到我们在 Pod nginx中挂载了一个类型为pv pvclaim01的卷。使用docker inspect可以查看它是如何挂载的:
"Mounts": [
{
"Source": "/var/lib/kubelet/pods/<id>/volumes/kubernetes.io~nfs/pvnfs01",
"Destination": "/usr/share/nginx/html",
"Mode": "",
"RW": true
},
...
]
Kubernetes 将/var/lib/kubelet/pods/<id>/volumes/kubernetes.io~nfs/<persistentvolume name>挂载到 Pod 中的目标位置。
使用存储类
在云计算环境中,人们动态地提供存储或数据卷。虽然PersistentVolumeClaim是基于管理员提供的现有静态PersistentVolume,但如果云卷能够在需要时动态请求,那将是非常有益的。存储类旨在解决这个问题。为了使存储类在集群中可用,需要满足三个条件。首先,必须启用DefaultStorageClass准入控制器(参见第七章,在 GCP 上构建 Kubernetes)。然后,PersistentVolumeClaim需要请求一个存储类。最后一个条件很简单;管理员必须配置一个存储类,以使动态提供工作:

StorageClass 动态分配一个 PV 并将其与 PVC 关联
默认存储类有多种,基本上是基于你的底层云提供商。存储类是定义底层存储提供商的抽象方式。它们基于不同类型的提供商具有不同的语法。默认存储类可以更改,但不能删除。默认存储类上有注解storageclass.beta.kubernetes.io/is-default-class=true。移除该注解可以禁用动态配置。将注解移动到另一个存储类可以切换默认存储类。如果没有存储类具有该注解,则在创建新的PersistentVolumeClaim时不会触发动态配置。
gcePersistentDisk
gcePersistentDisk卷将Google Compute Engine(GCE)Persistent Disk(PD)挂载到 Pod 中。如果你静态配置它,必须先使用gcloud命令或在 GCE 控制台中创建它。以下是示例:
# cat 2-6-8_gce/static_mount.yaml
apiVersion: v1
kind: Pod
metadata:
name: gce-pd-pod
spec:
containers:
- image: nginx
name: gce-pd-example
volumeMounts:
- mountPath: /mount-path
name: gce-pd
ports:
- containerPort: 80
volumes:
- name: gce-pd
gcePersistentDisk:
pdName: example
fsType: ext4
另外,更具成本效益的方法是使用动态配置。这样我们就不需要提前配置 PD。要启用动态配置,必须在 API 服务器上启用DefaultStorageClass准入控制器。在某些 Kubernetes 环境中,它已默认启用,例如在 GCE 中。我们也可以通过在 Pod/Deployment/ReplicaSet 配置文件中设置storageClassName: ""来显式禁用它。
接下来,我们将介绍如何创建一个非默认的StorageClass:
// list storageclasses (sc)
# kubectl get sc
NAME PROVISIONER
standard (default) kubernetes.io/gce-pd
我们可以看到我们有一个名为standard的默认存储类。如果这就是所需的提供商,那么你不需要创建自己的存储类。在以下示例中,我们将创建一个名为example的新存储类:
// gce storage class
# cat 2-6-8_gce/storageclass.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: example
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-standard
zones: us-central1-a
// create storage class
# kubectl create -f storageclass.yaml
storageclass "example" created
// check current storage classes
# kubectl get sc
NAME PROVISIONER
example kubernetes.io/gce-pd
standard (default) kubernetes.io/gce-pd
对于类型,你可以指定 GCE 支持的任何存储类型,例如pd-ssd。你也可以通过更改区域参数来指定区域。接下来,我们将添加一个PersistentVolumeClaim来使用这个存储类:
# 2-6-8_gce/pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gce-example
spec:
accessModes:
- ReadWriteOnce
storageClassName: example
resources:
requests:
storage: 5Gi
// create pvc
# kubectl create -f pvc.yaml
persistentvolumeclaim "gce-example" created
// check pvc status
# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
gce-example Bound pvc-d04218e3-ede5-11e7-aef7-42010a8001f4 5Gi RWO example 1h
这个配置文件将通过指定名为example的存储类来创建一个 PVC。一个 PV 将由该声明创建。当 PVC 处于Bound状态时,Kubernetes 将始终将该 PV 绑定到匹配的 PVC。接下来,让我们创建一个 Pod 来使用这个 PVC:
# cat 2-6-8_gce/pod.yaml
kind: Pod
apiVersion: v1
metadata:
name: gce-pd-pod
spec:
volumes:
- name: gce-pd
persistentVolumeClaim:
claimName: gce-example
containers:
- name: gce-pd-example
image: nginx
ports:
- containerPort: 80
volumeMounts:
- mountPath: /mount-path
name: gce-pd
// create a pod
# kubectl create -f pod.yaml
pod "gce-pd-pod" created
// check the volume setting in pod
# kubectl describe pod gce-pd-pod
...
Containers:
gce-pd-example:
Container ID:
Mounts:
/mount-path from gce-pd (rw)
...
Volumes:
gce-pd:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: gce-example
ReadOnly: false
我们可以看到gce-pd挂载在/mount-path下。让我们看看该卷是否已动态配置。
另外,你可以在 GCE 的命令行工具中使用gcloud compute disks list. gcloud。
awsElasticBlockStore
awsElasticBlockStore卷挂载Amazon Web Service Elastic Block Store(AWS EBS)卷。它是一个为 Amazon EC2 提供持久性块存储的服务。与 GCE 持久磁盘类似,我们可以静态或动态地配置它。
为了静态配置,管理员必须通过 AWS 控制台或 AWS CLI 提前创建一个 EBS 卷。以下是如何将现有 EBS 卷挂载到 Deployment 中容器的示例:
// example of how we used pre-created EBS volume.
# cat 2-6-8_aws/static_mount.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
name: aws-ebs-deployment
spec:
replicas: 2
selector:
matchLabels:
run: nginx
template:
metadata:
labels:
run: nginx
spec:
volumes:
- name: aws-ebs
awsElasticBlockStore:
volumeID: <ebs volume ID>
fsType: ext4
containers:
- name: aws-ebs-example
image: nginx
ports:
- containerPort: 80
volumeMounts:
- mountPath: /mount-path
name: aws-ebs
另一方面,要动态配置它,就像我们在 GCE 持久磁盘中演示的那样,我们首先创建一个非默认的存储类;你也可以自由使用默认存储类。在这里,我们的环境是通过 kops(github.com/kubernetes/kops;更多信息,请参见第六章,在 AWS 上构建 Kubernetes)进行配置的。环境已经绑定了所需的 IAM 策略,如ec2:AttachVolume、ec2:CreateVolume、ec2:DetachVolume和ec2:DeleteVolume。如果你从头开始配置,请确保你已将所需策略附加到主节点:
// declare a storage class
# cat 2-6-8_aws/storageclass.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: example-ebs
provisioner: kubernetes.io/aws-ebs
parameters:
type: io1
zones: us-east-1a
// create storage class
# kubectl create -f storageclass.yaml
storageclass "example-ebs" created
// check if example-ebs sc is created
# kubectl get sc
NAME PROVISIONER
default kubernetes.io/aws-ebs
example-ebs kubernetes.io/aws-ebs
gp2 (default) kubernetes.io/aws-ebs
接下来,我们创建一个 PVC,指定我们刚刚创建的存储类名称:
// declare a PVC
# cat 2-6-8_aws/pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: aws-example
spec:
accessModes:
- ReadWriteOnce
storageClassName: example-ebs
resources:
requests:
storage: 5Gi
// create a PVC
# kubectl create -f pvc.yaml
persistentvolumeclaim "aws-example" created
// check if PVC has been created
# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
aws-example Bound pvc-d1cddc08-ee31-11e7-8582-022bb4c3719e 5Gi RWO example-ebs 5s
当 Kubernetes 接收到PersistentVolumeClaim的请求时,它将尝试分配一个新的PersistentVolume,或者在可能的情况下绑定到现有的 PV:
// check if a PV is created by a PVC.
# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-d1cddc08-ee31-11e7-8582-022bb4c3719e 5Gi RWO Delete Bound default/aws-example example-ebs 36m
我们也可以在 AWS 控制台中检查对应的 PV。
最后,我们通过在 spec 中指定persistentVolumeClaim来创建一个 Deployment,并使用此卷:
// create a deployment
# cat 2-6-8_aws/deployment.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
name: aws-ebs-deployment
spec:
replicas: 2
selector:
matchLabels:
run: nginx
template:
metadata:
labels:
run: nginx
spec:
volumes:
- name: aws-ebs
persistentVolumeClaim:
claimName: aws-example
containers:
- name: aws-ebs-example
image: nginx
ports:
- containerPort: 80
volumeMounts:
- mountPath: /mount-path
name: aws-ebs
通过将claimName指定为aws-example,它将使用我们刚刚通过 PVC 创建的 EBS 卷,这个卷是动态请求到 AWS 的。如果我们使用kubectl describe pod <pod_name>查看 Pod 的描述,我们可以看到卷的详细信息:
// kubectl describe pod <pod_name>
# kubectl describe pod aws-ebs-deployment-68bdc6f546-246s7
Containers:
aws-ebs-example:
...
Mounts:
/mount-path from aws-ebs (rw)
Volumes:
aws-ebs:
Type: AWSElasticBlockStore (a Persistent Disk resource in AWS)
VolumeID: vol-0fccc3b0af8c17727
FSType: ext4
Partition: 0
ReadOnly: false
...
EBS 卷vol-0fccc3b0af8c17727被挂载在容器内的/mount-path路径下。
如果卷是动态配置的,默认的回收策略设置为delete。如果你希望在 PVC 被删除时仍然保留它们,可以将其设置为retain。
StorageObjectInUseProtection 准入控制器
即使 PVC 正在被 Pod 使用,用户也可能不小心删除它。在 Kubernetes v1.10 中,增加了一个新的准入控制器来防止这种情况发生。kubernetes.io/pv-protection或kubernetes.io/pvc-protection终结器将由StorageObjectInUseProtection准入控制器添加到 PV 或 PVC 中。然后,当发送对象删除请求时,准入控制器会进行预删除检查,看看是否有 Pod 正在使用它。这将防止数据丢失。
另请参见
卷可以通过在 Pod 或 ReplicaSet 的 spec 中声明来挂载到 Pod 上。查看以下食谱帮助你回忆:
-
第二章中的与 Pods 合作部分,走进 Kubernetes 概念
-
第二章中的与副本集合作部分,走进 Kubernetes 概念
-
第二章中的与 Secrets 合作部分,走进 Kubernetes 概念
-
第八章中的节点资源设置部分,高级集群管理
-
第八章中的认证与授权部分,高级集群管理
与 Secrets 合作
Kubernetes Secrets 以键值对格式管理信息,且值是经过编码的。它可以是密码、访问密钥或令牌。通过使用 Secrets,用户不需要在配置文件中暴露敏感数据。Secrets 可以减少凭据泄露的风险,使我们的资源配置更加有序。
目前有三种类型的 Secret:
-
Docker 注册表
-
TLS
通用/不透明是我们应用中使用的默认类型。Docker 注册表用于存储私有 Docker 注册表的凭据。TLS Secret 用于存储集群管理的 CA 证书包。
Kubernetes 会为用于访问 API 服务器的凭据创建内置的 Secret。
准备就绪
在使用 Secrets 之前,我们必须记住,Secret 应该总是在依赖的 Pod 之前创建,以便依赖的 Pod 可以正确引用它。此外,Secrets 有 1 MB 的大小限制。它适用于在单个 Secret 中定义大量信息。然而,Secret 并不设计用于存储大量数据。对于配置数据,建议使用 ConfigMaps。对于大量非敏感数据,建议使用卷(volumes)。
如何操作...
在下面的示例中,我们将演示如何创建一个通用/不透明类型的 Secret,并假设我们有一个需要在 Pod 中使用的访问令牌。
创建一个 Secret
创建 Secret 有两种方式。第一种是使用命令行中的 kubectl create secret,另一种是在配置文件中直接创建资源。
使用 kubectl create 命令行操作
通过使用 kubectl create secret 命令行,你可以从文件、目录或字面量值创建 Secret。使用这种方法时,你不需要自己编码 Secret,Kubernetes 会为你处理:
从文件中创建
- 如果 Secret 的源是文件,我们需要先创建一个包含敏感数据的文本文件:
// assume we have a sensitive credential named access token.
# cat 2-7-1_access-token
9S!g0U61699r
- 接下来,我们可以在命令行中使用
kubectl create secret来创建 Secret。语法如下:
Kubectl create secret <secret-type> --from-file <file1> (--from-file <file2> ...)
- 在我们的例子中,我们使用通用的 Secret 类型,因为访问令牌既不是 Docker 注册表的镜像拉取 Secret,也不是 TLS 信息:
# kubectl create secret generic access-token --from-file 2-7-1_access-token
secret "access-token" created
- 你可以使用
kubectl get secret命令查看详细的 Secret 信息:
// get the detailed information for a Secret.
# kubectl get secret access-token -o yaml
apiVersion: v1
data:
2-7-1_access-token: OVMhZzBVNjE2OTlyCg==
kind: Secret
metadata:
creationTimestamp: 2018-01-01T20:26:24Z
name: access-token
namespace: default
resourceVersion: "127883"
selfLink: /api/v1/namespaces/default/secrets/access-token
uid: 0987ec7d-ef32-11e7-ac53-080027ac331c
type: Opaque
- 你可以在 Linux 中使用
base64命令 (linux.die.net/man/1/base64) 来解码编码过的 Secret:
// decode encoded Secret
# echo "OVMhZzBVNjE2OTlyCg==" | base64 --decode
9S!g0U61699r
从目录中创建
从目录中创建 Secret 与从文件中创建类似,使用相同的命令,只是指定 directory。Kubernetes 会遍历目录中的所有文件并为你创建 Secret:
// show directory structure
# tree
.
├── 2-7-1_access-token-dir
│ └── 2-7-1_access-token
// create Secrets from a directory
# kubectl create secret generic access-token --from-file 2-7-1_access-token-dir/
secret "access-token" created
你可以再次使用 kubectl get secret access-token -o yaml 命令检查 Secret,并查看它们是否与文件中的相同。
从字面量值创建
Kubernetes 同样支持通过单个命令行创建 Secret:
// create a Secret via plain text in command line
# kubectl create secret generic access-token --from-literal=2-7-1_access-token=9S\!g0U61699r
secret "access-token" created
然后我们可以使用get secret命令检查它们是否与之前的方法相同:
// check the details of a Secret
# kubectl get secret access-token -o yaml
apiVersion: v1
data:
2-7-1_access-token: OVMhZzBVNjE2OTlyCg==
kind: Secret
metadata:
creationTimestamp: 2018-01-01T21:44:32Z
name: access-token
...
type: Opaque
通过配置文件
Secret 也可以通过配置文件直接创建;但是,你必须手动编码 Secret。只需使用 Secret 的类型:
// encode Secret manually
# echo '9S!g0U61699r' | base64
OVMhZzBVNjE2OTlyCg==
// create a Secret via configuration file, put encoded Secret into the file
# cat 2-7-1_secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: access-token
type: Opaque
data:
2-7-1_access-token: OVMhZzBVNjE2OTlyCg==
// create the resource
# kubectl create -f 2-7-1_secret.yaml
secret "access-token" created
在 Pod 中使用 Secrets
要在 Pod 中使用 Secrets,我们可以选择将其暴露为环境变量或将 Secrets 挂载为卷。
通过环境变量
在 Pod 内部访问 Secrets 时,可以在容器规格中添加env section,如下所示:
// using access-token Secret inside a Pod
# cat 2-7-2_env.yaml
apiVersion: v1
kind: Pod
metadata:
name: secret-example-env
spec:
containers:
- name: ubuntu
image: ubuntu
command: ["/bin/sh", "-c", "while : ;do echo $ACCESS_TOKEN; sleep 10; done"]
env:
- name: ACCESS_TOKEN
valueFrom:
secretKeyRef:
name: access-token
key: 2-7-1_access-token
// create a pod
# kubectl create -f 2-7-2_env.yaml
pod "secret-example-env" created
在上述示例中,我们将访问令牌 Secret 中的2-7-1_access-token键暴露为ACCESS_TOKEN环境变量,并通过一个无限循环打印出来。通过kubectl日志命令检查stdout:
// check stdout logs
# kubectl logs -f secret-example-env
9S!g0U61699r
注意,环境变量是在 Pod 创建时暴露的。如果 Secret 的新值被推送,必须重新启动或滚动更新 Pod 或 Deployment 以反映该更改。
如果我们描述secret-example-env Pod,可以看到已将一个环境变量设置为 Secret:
# kubectl describe pods secret-example-env
Name: secret-example-env
...
Environment:
ACCESS_TOKEN: <set to the key '2-7-1_access-token' in secret 'access-token'>
按卷
Secret 也可以通过使用 Secret 类型的卷进行挂载。以下是如何使用它的示例:
// example of using Secret volume
# cat 2-7-3_volumes.yaml
apiVersion: v1
kind: Pod
metadata:
name: secret-example-volume
spec:
containers:
- name: ubuntu
image: ubuntu
command: ["/bin/sh", "-c", "while : ;do cat /secret/token; sleep 10; done"]
volumeMounts:
- name: secret-volume
mountPath: /secret
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: access-token
items:
- key: 2-7-1_access-token
path: token
// create the Pod
kubectl create -f 2-7-3_volumes.yaml
pod "secret-example-volume" created
上述示例将在 Pod 的/secret mount点挂载secret-volume。/secret将包含一个名为 token 的文件,文件中包含我们的访问令牌。如果我们检查 Pod 详情,将显示我们挂载了一个只读的 Secret 卷:
// check the Pod details
# kubectl describe pods secret-example-volume
Name: secret-example-volume
...
Containers:
ubuntu:
...
Mounts:
/secret from secret-volume (ro)
...
Volumes:
secret-volume:
Type: Secret (a volume populated by a Secret)
SecretName: access-token
Optional: false
...
如果我们检查stdout,将显示 Pod 可以正确检索到预期的值:
# kubectl logs -f secret-example-volume
9S!g0U61699r
与环境变量一样,挂载卷中的文件是在 Pod 创建时创建的。当 Pod 创建时间后更新 Secret 值时,它不会动态更改。
删除 Secret
要删除 Secret,只需使用kubectl delete secret命令:
# kubectl delete secret access-token
secret "access-token" deleted
如果在 Secret 卷已附加时删除 Secret,当卷引用消失时,它将显示错误信息:
# kubectl describe pods secret-example-volume
...
Events:
Warning FailedMount 53s (x8 over 1m) kubelet, minikube MountVolume.SetUp failed for volume "secret-volume" : secrets "access-token" not found
它是如何工作的...
为了减少泄露 Secrets 内容的风险,Secret 不会写入磁盘。相反,kubelet 在节点上创建一个tmpfs文件系统来存储 Secret。Kubernetes API 服务器将 Secret 推送到运行所需容器的节点。当容器销毁时,数据将被清除。
还有更多...
Secrets 存储少量敏感数据。对于应用程序配置,建议使用ConfigMaps存储非敏感信息。
使用 ConfigMaps
下面是使用ConfigMaps的示例:
# cat configmap/2-7-4_configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: config-example
data:
app.properties: |
name=kubernetes-cookbook
port=443
// create configmap
# kubectl create -f configmap/2-7-4_configmap.yaml
configmap "config-example" created
类似于 Secret,ConfigMaps可以通过环境变量或卷来获取:
# cat configmap/2-7-4_env.yaml
apiVersion: v1
kind: Pod
metadata:
name: configmap-env
spec:
containers:
- name: configmap
image: ubuntu
command: ["/bin/sh", "-c", "while : ;do echo $APP_NAME; sleep 10; done"]
env:
- name: APP_NAME
valueFrom:
configMapKeyRef:
name: config-example
key: app.properties
// create the pod
#kubectl create -f configmap/2-7-4_env.yaml
pod "configmap-env" created
或者,你可以使用ConfigMaps卷来获取配置信息:
// using configmap in a pod
# cat configmap/2-7-4_volumes.yaml
apiVersion: v1
kind: Pod
metadata:
name: configmap-volume
spec:
containers:
- name: configmap
image: ubuntu
command: ["/bin/sh", "-c", "while : ;do cat /src/app/config/app.properties; sleep 10; done"]
volumeMounts:
- name: config-volume
mountPath: /src/app/config
volumes:
- name: config-volume
configMap:
name: config-example
将 Secrets 和 ConfigMap 挂载到同一卷中
Projected volume 是一种将多个卷源组合到同一挂载点的方式。目前,它支持 Secrets、ConfigMap和downwardAPI。
以下是我们在本章中使用的 Secrets 和ConfigMaps示例如何分组的示例:
// using projected volume
# cat 2-7-5_projected_volume.yaml
apiVersion: v1
kind: Pod
metadata:
name: projected-volume-example
spec:
containers:
- name: container-tes
image: ubuntu
command: ["/bin/sh", "-c", "while : ;do cat /projected-volume/configmap && cat /projected-volume/token; sleep 10; done"]
volumeMounts:
- name: projected-volume
mountPath: "/projected-volume"
volumes:
- name: projected-volume
projected:
sources:
- secret:
name: access-token
items:
- key: 2-7-1_access-token
path: token
- configMap:
name: config-example
items:
- key: app.properties
path: configmap
// create projected volume
# kubectl create -f 2-7-5_projected_volume.yaml
pod "projected-volume-example" created
让我们检查stdout,看看它是否正常工作:
# kubectl logs -f projected-volume-example
name=kubernetes-cookbook
port=443
9S!g0U61699r
另见
-
第二章,Kubernetes 概念解析 中的 工作与 Volumes 部分
-
第三章,玩转容器 中的 配置文件操作 部分
-
第五章,构建持续交付管道 中的 将单体迁移到微服务 和 与私有 Docker 注册表交互 部分
-
第七章,在 GCP 上构建 Kubernetes 中的 kubeconfig 的高级设置 部分
使用名称
当你创建任何 Kubernetes 对象,例如 Pod、Deployment 和 Service 时,你可以为其指定一个名称。Kubernetes 中的名称是空间唯一的,这意味着你不能在 Pods 中分配相同的名称。
准备好
Kubernetes 允许我们为名称设置以下限制:
-
最多 253 个字符
-
小写字母和数字字符
-
中间可以包含特殊字符,但只能包含破折号 (-) 和点 (.)
如何做...
为 Pod 分配名称,请按照以下步骤操作:
- 以下示例是 Pod 的 YAML 配置,将 Pod 名称指定为
my-pod,容器名称指定为my-container;你可以如下成功创建它:
# cat my-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: my-container
image: nginx
# kubectl create -f my-pod.yaml
pod "my-pod" created
# kubectl get pods
NAME READY STATUS RESTARTS AGE
my-pod 0/1 Running 0 4s
- 你可以使用
kubectl describe命令查看名为my-container的容器,如下所示:
$ kubectl describe pod my-pod
Name: my-pod
Namespace: default
Node: minikube/192.168.64.12
Start Time: Sat, 16 Dec 2017 10:53:38 -0800
Labels: <none>
Annotations: <none>
Status: Running
IP: 172.17.0.3
Containers:
my-container:
Container ID: docker://fcf36d0a96a49c5a08eb6de1ef27ca761b4ca1c6b4a3a4312df836cb8e0a5304
Image: nginx
Image ID: docker-pullable://nginx@sha256:2ffc60a51c9d658594b63ef5acfac9d92f4e1550f633a3a16d898925c4e7f5a7
Port: <none>
State: Running
Started: Sat, 16 Dec 2017 10:54:43 -0800
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-lmd62 (ro)
Conditions:
Type Status
Initialized True
Ready True
PodScheduled True
Volumes:
default-token-lmd62:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-lmd62
Optional: false
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 1m default-scheduler Successfully assigned my-pod to minikube
Normal SuccessfulMountVolume 1m kubelet, minikube MountVolume.SetUp succeeded for volume "default-token-lmd62"
Normal Pulling 1m kubelet, minikube pulling image "nginx"
Normal Pulled 50s kubelet, minikube Successfully pulled image "nginx"
Normal Created 50s kubelet, minikube Created container
Normal Started 50s kubelet, minikube Started container
- 另一方面,以下示例包含两个容器,但分配了相同的名称
my-container;因此,kubectl create命令会返回错误,无法创建 Pod:
//delete previous Pod
$ kubectl delete pod --all
pod "my-pod" deleted
$ cat duplicate.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: my-container
image: nginx
- name: my-container
image: centos
command: ["/bin/sh", "-c", "while : ;do curl http://localhost:80/; sleep 3; done"]
$ kubectl create -f duplicate.yaml
The Pod "my-pod" is invalid: spec.containers[1].name: Duplicate value: "my-container"
你可以添加 --validate 标志。
例如,命令 kubectl create -f duplicate.yaml --validate 使用模式在发送之前验证输入。
另一个示例中,YAML 包含一个 ReplicationController 和一个 Service,它们都使用相同的名称 my-nginx,但由于 Deployment 和 Service 是不同的对象,因此可以成功创建:
$ cat my-nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
replicas: 3
selector:
matchLabels:
run: my-label
template:
metadata:
labels:
run: my-label
spec:
containers:
- name: my-container
image: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: my-nginx
spec:
ports:
- protocol: TCP
port: 80
type: NodePort
selector:
run: my-label
//create Deployment and Service
$ kubectl create -f my-nginx.yaml
deployment.apps "my-nginx" created
service "my-nginx" created
//Deployment "my-nginx" is created
$ kubectl get deploy
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
my-nginx 3 3 3 3 1m
//Service "my-nginx" is also created
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 13d
my-nginx NodePort 10.0.0.246 <none> 80:31168/TCP 1m
它是如何工作的...
名称只是一个唯一标识符,所有命名约定都可以使用;然而,建议查找并确认容器镜像。例如:
-
memcached-pod1 -
haproxy.us-west -
my-project1.mysql
另一方面,以下示例因 Kubernetes 限制而无法使用:
-
Memcache-pod1(包含大写字母) -
haproxy.us_west(包含下划线) -
my-project1.mysql.(最后有点)
注意,Kubernetes 支持标签,它允许分配 key=value 样式的标识符,也允许重复。因此,如果你想分配以下信息,请改用标签:
-
环境(例如:staging,production)
-
版本(例如:v1.2)
-
应用角色(例如:frontend,worker)
此外,Kubernetes 还支持具有不同命名空间的名称。这意味着你可以在不同的命名空间中使用相同的名称(例如:nginx)。因此,如果你只想分配一个应用名称,可以使用命名空间来代替。
参见
本章中的这一部分描述了如何分配和查找对象的名称。这只是一个基本的方法论,但 Kubernetes 还有更强大的命名工具,如命名空间(Namespace)和选择器(selectors),用于管理集群:
-
使用 Pods
-
部署 API
-
使用服务
-
使用命名空间
-
使用标签和选择器
使用命名空间
在 Kubernetes 集群中,资源的名称在命名空间内是唯一标识符。使用 Kubernetes 命名空间可以将不同环境的用户空间隔离开,即使它们在同一个集群中。它为你提供了创建隔离环境和将资源分配给不同项目和团队的灵活性。你可以将命名空间看作是一个虚拟集群。Pods、Services 和 Deployments 被包含在特定的命名空间中。一些低级资源,如节点和 persistentVolumes,不属于任何命名空间。
在深入了解资源命名空间之前,让我们先了解一下 kubeconfig 和一些关键字:

kubeconfig 组件的关系
kubeconfig 用于调用配置 Kubernetes 集群访问权限的文件。作为系统的原始配置,Kubernetes 将 $HOME/.kube/config 视为 kubeconfig 文件。前面图示所说明的一些概念如下:
-
kubeconfig 定义用户、集群和上下文:
kubeconfig列出了多个用户以定义认证,并列出了多个集群以指示 Kubernetes API 服务器。此外,kubeconfig中的上下文是用户和集群的组合:使用某种认证访问特定的 Kubernetes 集群。 -
用户和集群可以在不同的上下文之间共享:在前面的图示中,上下文 1 和 上下文 3 都以 用户 1 作为用户内容。然而,每个上下文只能有一个用户和一个集群定义。
-
命名空间可以附加到上下文:每个上下文都可以分配给一个现有的命名空间。如果没有,如 上下文 3,则与默认命名空间
default一起使用。 -
当前上下文是客户端的默认环境:我们在
kubeconfig中可能有多个上下文,但只有一个是当前上下文。当前上下文和附加在其上的命名空间将构建用户的默认计算环境。
现在你应该明白,正如名称空间与kubeconfig一起工作一样,用户可以通过切换kubeconfig中的当前上下文轻松切换默认资源的使用。然而,用户仍然可以在不同的名称空间中启动任何资源,只需指定名称空间即可。在本教程中,你将学习如何创建自己的名称空间,并如何使用它。
准备工作
默认情况下,Kubernetes 已创建名为default的名称空间。所有未指定名称空间的对象将被放入default名称空间。Kubernetes 还将创建另一个初始名称空间kube-system,用于存放 Kubernetes 系统对象,如附加组件或覆盖网络。尝试列出所有名称空间:
// check all Namespaces, "ns" is the resource abbreviation of Namespace
$ kubectl get ns
NAME STATUS AGE
default Active 15d
kube-public Active 15d
kube-system Active 15d
你可能会在初始阶段发现一个附加的名称空间kube-public。它是为即使没有权限访问 Kubernetes 系统的用户提供一些公共配置而设计的。minikube和kubeadm这两个工具都会在系统启动时创建它。
名称空间(Namespace)的名称必须是一个 DNS 标签,并遵循以下规则:
-
最多 63 个字符
-
匹配正则表达式 a-z0-9
如何操作...
在本节中,我们将演示如何创建名称空间、更改默认名称空间以及删除名称空间。
创建名称空间
创建名称空间的步骤如下:
- 在确定名称空间的期望名称后,让我们使用配置文件创建它:
$ cat my-first-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: my-namespace
// create the resource by subcommand "create"
$ kubectl create -f my-first-namespace.yaml
namespace "my-namespace" created
// list the namespaces again
$ kubectl get ns
NAME STATUS AGE
default Active 16d
kube-public Active 16d
kube-system Active 16d
my-namespace Active 6s
- 现在,你可以看到我们有一个名为
my-namespace的附加名称空间。接下来,让我们在这个新名称空间中运行一个 Kubernetes 部署:
// run a Deployment with a flag specifying Namespace
$ kubectl run my-nginx --image=nginx --namespace=my-namespace
deployment.apps "my-nginx" created
- 在尝试检查新创建的资源时,我们通常无法轻松找到它们:
$ kubectl get deployment
No resources found.
- 相反,部署将显示与名称空间相关的标志:
// list any Deployment in all Namespaces
$ kubectl get deployment --all-namespaces
NAMESPACE NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
kube-system calico-kube-controllers 1 1 1 1 16d
kube-system calico-policy-controller 0 0 0 0 16d
kube-system kube-dns 1 1 1 1 16d
my-namespace my-nginx 1 1 1 1 1m
// get Deployments from my-namespace
$ kubectl get deployment --namespace=my-namespace
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
my-nginx 1 1 1 1 1m
现在,你可以找到刚刚创建的资源。
更改默认名称空间
如前所述,我们可以通过将kubeconfig中的当前上下文切换到另一个上下文来更改默认的名称空间:
- 首先,我们可以使用子命令
config检查当前上下文:
// check the current context in kubeconfig
$ kubectl config current-context
kubernetes-admin@kubernetes
当检查当前上下文时,你可能对输出感到不熟悉。前述的当前上下文是由kubeadm定义和创建的。如果你使用minikube作为你的 Kubernetes 系统管理工具,你可能会在屏幕上看到minikube。
- 无论你在检查
kubeconfig中的当前上下文时得到了什么,使用子命令config set-context来创建一个新上下文:
// create a new context called "my-context"
// the new context is going to follow the cluster and the user of current context, but attached with new Namespace
//This is for kubeadm environment
$ kubectl config set-context my-context --namespace=my-namespace --cluster=kubernetes --user=kubernetes-admin
Context "my-context" created.
- 上述命令是基于
kubeadm管理的 Kubernetes;你也可以对minikube执行类似的命令,前提是kubeconfig中使用的是默认集群和用户的名称:
// for minikube environemt
$ kubectl config set-context my-context --namespace=my-namespace --cluster=minikube --user=minikube
- 接下来,检查
kubeconfig以验证更改:
//check kubectlconfig for the new context
$ kubectl config view
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: REDACTED
server: https://192.168.122.101:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
- context:
cluster: kubernetes namespace: my-namespace user: kubernetes-admin name: my-context current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
user:
client-certificate-data: REDACTED
client-key-data: REDACTED
检查kubeconfig的配置时,在上下文部分,你可以找到一个与我们定义的上下文完全相同,并且包含我们新创建的名称空间。
- 执行以下命令以切换到使用新上下文:
$ kubectl config use-context my-context
Switched to context "my-context".
// check current context
$ kubectl config current-context
my-context
现在当前上下文是我们自定义的一个,其中包含命名空间 my-namespace。
- 由于默认命名空间更改为
my-namespace,我们可以不指定命名空间来获取部署:
$ kubectl get deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
my-nginx 1 1 1 1 20m
//double check the namespace of resource
$ kubectl describe deployment my-nginx
Name: my-nginx
Namespace: my-namespace CreationTimestamp: Mon, 18 Dec 2017 15:39:46 -0500
Labels: run=my-nginx
:
(ignored)
删除命名空间
如果您在前面的 Kubernetes 资源页面中有所了解,可以知道子命令 delete 用于删除资源。在删除命名空间时也可以使用它,这将删除其下的所有资源:
// first, go ahead to remove the Namespace "my-namespace"
$ kubectl delete ns my-namespace
namespace "my-namespace" deleted
// check the Deployment again, the exited "my-nginx" is terminated
$ kubectl get deployment
No resources found.
// while trying to create anything, the error message showing the default Namespace is not existed
$ kubectl run my-alpine --image=alpine
Error from server (NotFound): namespaces "my-namespace" not found
要解决此问题,您可以将另一个命名空间附加到当前上下文,或者只需将当前上下文更改为之前的上下文:
// first solution: use set-context to update the Namespace
// here we just leave Namespace empty, which means to use default Namespace
$ kubectl config set-context my-context --namespace=""
Context "my-context" modified.
// second solution: switch current context to another context
// in this case, it is kubeadm environment
$ kubectl config use-context kubernetes-admin@kubernetes
Switched to context "kubernetes-admin@kubernetes".
工作原理如下…
虽然我们讨论了 kubeconfig 中的命名空间和上下文,但它们在 Kubernetes 系统中是独立的对象。kubeconfig 的上下文是客户端的概念,只能由特定用户控制,它使得在命名空间和集群中工作更加简便。另一方面,命名空间是服务器端的概念,用于在集群中进行资源隔离,并且可以在客户端之间共享。
还有更多内容…
我们不仅利用命名空间来分隔资源,还可以实现更精细的计算资源配置。通过限制命名空间的计算资源使用量,系统管理员可以避免客户端创建过多资源导致服务器超载。
创建限制范围
要设置每个命名空间的资源限制,应在 Kubernetes API 服务器中添加接入控制器 LimitRanger。如果您的系统管理器是 minikube 或 kubeadm,则无需担心此设置。
Kubernetes API 服务器中的接入控制器 接入控制器是 Kubernetes API 服务器中的一种设置,用于定义 API 服务器中的高级功能。在接入控制器中可以设置多种功能。用户可以在启动 API 服务器时通过配置文件或使用 CLI 标志 --admission-control 添加这些功能。依赖于 minikube 或 kubeadm 进行系统管理时,它们在接入控制器中具有各自的初始设置:
-
kubeadm 中的默认接入控制器:
Initializers、NamespaceLifecycle、LimitRanger、ServiceAccount、PersistentVolumeLabel、DefaultStorageClass、DefaultTolerationSeconds、NodeRestriction、ResourceQuota -
minikube 中的默认接入控制器:
NamespaceLifecycle、LimitRanger、ServiceAccount、DefaultStorageClass、ResourceQuota
根据您的 API 服务器版本,在官方文档的建议列表中可以找到详细内容,网址为 kubernetes.io/docs/admin/admission-controllers/#is-there-a-recommended-set-of-admission-controllers-to-use。获取更多想法!
一个简单的命名空间没有资源配额限制。最初,我们启动一个命名空间并查看其初始设置:
// create a Namespace by YAML file
$ kubectl create -f my-first-namespace.yaml
namespace "my-namespace" created
$ kubectl describe ns my-namespace
Name: my-namespace
Labels: <none>
Annotations: <none>
Status: Active
No resource quota.
No resource limits.
之后,我们创建一个名为LimitRange的资源来指定命名空间的资源限制。以下是在命名空间中创建限制的一个好例子:
$ cat my-first-limitrange.yaml
apiVersion: v1
kind: LimitRange
metadata:
name: my-limitrange
spec:
limits:
- type: Pod
max:
cpu: 2
memory: 1Gi
min:
cpu: 200m
memory: 6Mi
- type: Container
default:
cpu: 300m
memory: 200Mi
defaultRequest:
cpu: 200m
memory: 100Mi
max:
cpu: 2
memory: 1Gi
min:
cpu: 100m
memory: 3Mi
然后,我们将在 Pod 中限制资源,设置 CPU 的最大值为2,最小值为200m,内存的最大值为1Gi,最小值为6Mi。对于容器,CPU 的限制范围为100m - 2,内存的限制范围为3Mi - 1Gi。如果设置了最大值,则必须在 Pod/容器的规范中指定限制;如果设置了最小值,则必须在 Pod/容器创建时指定请求值。LimitRange 中的default和defaultRequest部分用于指定容器规范中的默认限制和请求。
LimitRange 中的 CPU 限制值 文件my-first-limitrange.yaml中,Pod 的2和200m的限制值是什么意思?整数值表示 CPU 的数量;值中的“m”表示毫核,因此200m表示 0.2 CPU(200 * 0.001)。类似地,容器的默认 CPU 限制为 0.2 到 0.3,实际限制为 0.1 到 2。
之后,我们将在我们的简单命名空间中创建 LimitRange,并检查会发生什么:
// create the limitrange by file with the flag of Namespace
// the flag --namespace can be abbreviated to "n"
$ kubectl create -f my-first-limitrange.yaml -n my-namespace
limitrange "my-limitrange" created
// check the resource by subcommand "get"
$ kubectl get limitrange -n my-namespace
NAME AGE
my-limitrange 23s
// check the customized Namespace
$ kubectl describe ns my-namespace
Name: my-namespace
Labels: <none>
Annotations: <none>
Status: Active
No resource quota.
Resource Limits
Type Resource Min Max Default Request Default Limit Max Limit/Request Ratio
---- -------- --- --- --------------- ------------- -----------------------
Pod cpu 200m 2 - - -
Pod memory 6Mi 1Gi - - -
Container memory 3Mi 1Gi 100Mi 200Mi -
Container cpu 100m 2 200m 300m -
当你查询my-namespace的详细描述时,你将直接看到附加到命名空间的约束条件。无需添加 LimitRange。现在,所有在该命名空间中创建的 Pod 和容器必须遵循这里列出的资源限制。如果定义违反规则,将会抛出验证错误:
// Try to request an overcommitted Pod, check the error message
$ kubectl run my-greedy-nginx --image=nginx --namespace=my-namespace --restart=Never --requests="cpu=4"
The Pod "my-greedy-nginx" is invalid: spec.containers[0].resources.requests: Invalid value: "4": must be less than or equal to cpu limit
删除 LimitRange
我们可以使用子命令delete删除 LimitRange 资源。与创建 LimitRange 类似,在命名空间中删除LimitRange会自动移除该命名空间中的约束:
$ kubectl delete -f my-first-limitrange.yaml -n=my-namespace
limitrange "my-limitrange" deleted
$ kubectl describe ns my-namespace
Name: my-namespace
Labels: <none>
Annotations: <none>
Status: Active
No resource quota.
No resource limits.
另见
许多 Kubernetes 资源可以在命名空间下运行。为了实现良好的资源管理,请查看以下方法:
-
与 Pods 的工作
-
部署 API
-
与名称的工作
-
在第七章,在 GCP 上构建 Kubernetes中的节点资源设置部分
与标签和选择器的工作
标签是一组键/值对,它们附加到对象的元数据中。我们可以使用标签来选择、组织和分组对象,例如 Pods、ReplicaSets 和 Services。标签不一定是唯一的,对象可以携带相同的标签集。
标签选择器用于查询具有以下类型标签的对象:
-
基于等式:
- 使用等号(
=或==)或不等号(!=)运算符
- 使用等号(
-
基于集合:
- 使用
in或notin运算符
- 使用
准备就绪
在设置对象的标签之前,你应该考虑键和值的有效命名约定。
有效的键应该遵循以下规则:
-
名称可选地带有前缀,前缀和名称之间用斜杠分隔。
-
前缀必须是一个 DNS 子域名,用点分隔,且不能超过 253 个字符。
-
名称必须少于 63 个字符,且由 [a-z0-9A-Z] 字符、破折号、下划线和点组成。请注意,符号如果放在开头或结尾是非法的。
有效值应遵循以下规则:
- 名称必须少于 63 个字符,且由 [a-z0-9A-Z] 字符、破折号、下划线和点组成。请注意,符号如果放在开头或结尾是非法的。
你还应该考虑目的。例如,有两个项目,pilot 和 poc。这些项目处于不同的环境中,如 develop 和 production。此外,一些项目包含多个层次,如 frontend、cache 和 backend。我们可以将标签的键值对组合如下:
labels:
project: pilot
environment: develop
tier: frontend
如何操作...
- 让我们尝试创建几个带有之前标签的 Pods,以区分不同的项目、环境和层次,如下所示:
| YAML 文件名 | Pod 镜像 | 项目 | 环境 | 层次 |
|---|---|---|---|---|
pilot-dev.yaml |
nginx |
pilot | develop | frontend |
pilot-dev.yaml |
memcached |
cache |
||
pilot-prod.yaml |
nginx |
production | frontend |
|
pilot-prod.yaml |
memcached |
cache |
||
poc-dev.yaml |
httpd |
poc | develop | frontend |
poc-dev.yaml |
memcached |
cache |
- 为了方便起见,我们将准备三个包含两个 Pods 的 YAML 文件,Pods 之间用
YAML 分隔符 ---分开:
pilot-dev.yaml:
apiVersion: v1
kind: Pod
metadata:
name: pilot.dev.nginx
labels:
project: pilot
environment: develop
tier: frontend
spec:
containers:
- name: nginx
image: nginx
---
apiVersion: v1
kind: Pod
metadata:
name: pilot.dev.memcached
labels:
project: pilot
environment: develop
tier: cache
spec:
containers:
- name: memcached
image: memcached
pilot-prod.yaml:
apiVersion: v1
kind: Pod
metadata:
name: pilot.prod.nginx
labels:
project: pilot
environment: production
tier: frontend
spec:
containers:
- name : nginx
image: nginx
---
apiVersion: v1
kind: Pod
metadata:
name: pilot.prod.memcached
labels:
project: pilot
environment: production
tier: cache
spec:
containers:
- name: memcached
image: memcached
poc-dev.yaml:
apiVersion: v1
kind: Pod
metadata:
name: poc.dev.httpd
labels:
project: poc
environment: develop
tier: frontend
spec:
containers:
- name: httpd
image: httpd
---
apiVersion: v1
kind: Pod
metadata:
name: poc.dev.memcached
labels:
project: poc
environment: develop
tier: cache
spec:
containers:
- name: memcached
image: memcached
- 使用
kubectl create命令创建这六个 Pods,如下所示,查看标签是如何定义的:
$ kubectl create -f pilot-dev.yaml
pod "pilot.dev.nginx" created
pod "pilot.dev.memcached" created
$ kubectl create -f pilot-prod.yaml
pod "pilot.prod.nginx" created
pod "pilot.prod.memcached" created
$ kubectl create -f poc-dev.yaml
pod "poc.dev.httpd" created
pod "poc.dev.memcached" created
- 运行
kubectl describe <Pod 名称>来查看标签,如下所示。它看起来不错,所以我们可以使用标签选择器按不同的标准查询这些 Pods:
$ kubectl describe pod poc.dev.memcache
Name: poc.dev.memcached
Namespace: default
Node: minikube/192.168.99.100
Start Time: Sun, 17 Dec 2017 17:23:15 -0800
Labels: environment=develop
project=poc
tier=cache
Annotations: <none>
Status: Running
...
它是如何工作的...
如本节前面所述,标签选择器有两种类型:基于等式的和基于集合的。这些类型有不同的操作符来指定标准。
基于等式的标签选择器
基于等式的选择器可以指定等于或不等于,并且使用逗号添加更多的标准。使用 -l 或 --selector 选项来指定这些标准,以过滤对象的名称;例如:
- 查询属于 pilot 项目的 Pods:
$ kubectl get pods -l "project=pilot"
NAME READY STATUS RESTARTS AGE
pilot.dev.memcached 1/1 Running 0 21m
pilot.dev.nginx 1/1 Running 0 21m
pilot.prod.memcached 1/1 Running 0 21m
pilot.prod.nginx 1/1 Running 0 21m
- 查询属于
frontend层次的 Pods:
$ kubectl get pods -l "tier=frontend"
NAME READY STATUS RESTARTS AGE
pilot.dev.nginx 1/1 Running 0 21m
pilot.prod.nginx 1/1 Running 0 21m
poc.dev.httpd 1/1 Running 0 21m
- 查询属于
frontend层次并且处于开发环境中的 Pods:
$ kubectl get pods -l "tier=frontend,environment=develop"
NAME READY STATUS RESTARTS AGE
pilot.dev.nginx 1/1 Running 0 22m
poc.dev.httpd 1/1 Running 0 21m
- 查询属于
frontend层次且不在开发环境中的 Pods:
$ kubectl get pods -l "tier=frontend,environment!=develop"
NAME READY STATUS RESTARTS AGE
pilot.prod.nginx 1/1 Running 0 29m
基于集合的标签选择器
使用基于集合的选择器时,你可以使用 in 或 notin 操作符,这类似于 SQL IN 子句,可以指定多个关键字,如以下示例所示:
- 查询属于
pilot项目的Pods:
$ kubectl get pods -l "project in (pilot)"
NAME READY STATUS RESTARTS AGE
pilot.dev.memcached 1/1 Running 0 36m
pilot.dev.nginx 1/1 Running 0 36m
pilot.prod.memcached 1/1 Running 0 36m
pilot.prod.nginx 1/1 Running 0 36m
- 查询属于 pilot 项目和
frontend层次的Pods:
$ kubectl get pods -l "project in (pilot), tier in (frontend)"
NAME READY STATUS RESTARTS AGE
pilot.dev.nginx 1/1 Running 0 37m
pilot.prod.nginx 1/1 Running 0 37m
- 查询属于
pilot项目并且属于frontend或cache层次的Pods:
$ kubectl get pods -l "project in (pilot), tier in (frontend,cache)"
NAME READY STATUS RESTARTS AGE
pilot.dev.memcached 1/1 Running 0 37m
pilot.dev.nginx 1/1 Running 0 37m
pilot.prod.memcached 1/1 Running 0 37m
pilot.prod.nginx 1/1 Running 0 37m
- 查询属于试点项目且不属于
frontend或backend层的Pods(注意,我们没有创建backend层对象):
$ kubectl get pods -l "project in (pilot), tier notin (frontend, backend)"
NAME READY STATUS RESTARTS AGE
pilot.dev.memcached 1/1 Running 0 50m
pilot.prod.memcached 1/1 Running 0 50m
如前面例子所示,对于基于相等和基于集合的标签选择器,基于相等的选择器更简单,而基于集合的选择器则更具表现力。注意,你可以像下面这样混合使用这两种运算符:
- 查询不属于试点项目和开发环境的 Pods:
$ kubectl get pods -l "project notin (pilot), environment=develop"
NAME READY STATUS RESTARTS AGE
poc.dev.httpd 1/1 Running 0 2m
poc.dev.memcached 1/1 Running 0 2m
所以,你可以使用最有效的方式来筛选出 Kubernetes 对象。此外,你还可以使用一种或两种选择器来配置 Kubernetes Service、Deployments 等。然而,一些对象只支持基于相等的选择器,而一些对象支持两者。所以,让我们来看看如何定义它。
还有更多...
标签选择器不仅有助于列出对象,还可以指定 Kubernetes Service 和 Deployment 来绑定对象。
使用标签选择器将 Service 与 Pods 或 ReplicaSets 连接
从 Kubernetes 1.9 版本开始,Service 仅支持基于相等的选择器来绑定到 Pods 或 ReplicaSet。
让我们创建一个绑定到nginx的 Service,它属于生产环境和试点项目。记住,nginx也属于前端层:
//check your selector filter is correct or not
$ kubectl get pods -l 'environment=production,project=pilot,tier=frontend'
NAME READY STATUS RESTARTS AGE
pilot.prod.nginx 1/1 Running 0 19m
//create Service yaml that specify selector
$ cat pilot-nginx-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: pilot-nginx-svc
spec:
type: NodePort
ports:
- protocol: TCP
port: 80
selector:
project: pilot
environment: production tier: frontend
//create pilot-nginx-svc
$ kubectl create -f pilot-nginx-svc.yaml
service "pilot-nginx-svc" created
这里是等效的,你可以使用kubectl expose命令来指定标签选择器:
$ kubectl expose pod pilot.prod.nginx --name=pilot-nginx-svc2 --type=NodePort --port=80 --selector="project=pilot,environment=develop,tier=frontend"
service "pilot-nginx-svc2" exposed
根据你的 Kubernetes 环境,如果你使用的是 minikube,检查你的 Service 会更容易,可以通过minikube service <Service name>来查看,正如下面的截图所示。如果你没有使用 minikube,可以访问任何 Kubernetes 节点并查看分配的 Service 端口号。对于下面的截图,它将是<node ip>:31981或<node ip>:31820:

访问在 minikube 上运行的 Service
使用基于集合的选择器将 Deployment 与 ReplicaSet 连接
部署不仅支持基于相等的选择器,还支持基于集合的选择器,以指定ReplicaSet。为此,你可以写spec.selector.matchExpressions[]来指定键和值,以及in/notin运算符。例如,如果你想指定project in (poc),environment in (staging),tier notin (backend,cache),那么matchExpressions将如下所示:
$ cat deploy_set_selector.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
replicas: 3
selector:
matchExpressions:
- {key: project, operator: In, values: [poc]}
- {key: environment, operator: In, values: [staging]}
- {key: tier, operator: NotIn, values: [backend,cache]}
template:
metadata:
labels:
project: poc
environment: staging
tier: frontend
spec:
containers:
- name: my-nginx
image: nginx
ports:
- containerPort: 80
如你所见,YAML 数组表示为*-*,而映射对象表示为{},用于指定键、运算符和值。注意,值也将是一个数组,因此使用方括号[]来指定一个或多个值。
你需要注意的一件事是一个名为pod-template-hash的标签,这是由 Deployment 创建的。当你创建一个 Deployment 时,它也会创建一个ReplicaSet对象。这时,Deployment 还会将pod-template-hash标签分配给ReplicaSet。让我们看看它是如何工作的:
$ kubectl create -f deploy_set_selector.yaml
deployment.apps "my-nginx" created
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
my-nginx2-764d7cfff 3 3 3 19s
$ kubectl describe rs my-nginx2-764d7cfff
Name: my-nginx2-764d7cfff
Namespace: default
Selector: environment in (staging),pod-template-hash=320837999,project in (poc),tier notin (backend,cache)
...
...
Pod Template:
Labels: environment=staging
pod-template-hash=320837999
project=poc
tier=frontend
...
...
如你所见,ReplicaSet my-nginx2-764d7cfff 使用基于相等性的选择器,因为 pod-template-hash=320837999 被附加到选择器和 Pod 模板中。它将用于生成带有特定哈希函数的 ReplicaSet 和 Pod 名称(例如,my-nginx2-764d7cfff)。
另见
在这一节中,你学到了为 Kubernetes 对象分配标签的灵活性。此外,基于相等性和集合的选择器允许我们通过标签过滤对象。选择器非常重要,可以松散地将对象(如 Service 和 ReplicaSet/Pod 以及 Deployment 和 ReplicaSet)进行耦合。
接下来的章节也将使用标签和选择器的概念来管理容器:
-
更新在线容器 章节在 第三章,玩转容器
-
管理 Kubernetes 集群在 GKE 上 章节在 第七章,在 GCP 上构建 Kubernetes
第三章:玩转容器
本章将介绍以下内容:
-
扩展你的容器
-
更新正在运行的容器
-
转发容器端口
-
确保容器的灵活使用
-
在 Kubernetes 上提交任务
-
配置文件操作
介绍
在谈论容器管理时,你需要了解与应用程序包管理(如rpm/dpkg)的一些区别,因为你可以在同一台机器上运行多个容器。你还需要注意网络端口冲突。本章将介绍如何使用 Kubernetes 更新、扩展和启动容器应用程序。
扩展你的容器
根据预定义标准对应用或服务进行扩展和缩减是利用计算资源的高效方式之一。在 Kubernetes 中,你可以手动扩展和缩减,也可以使用水平 Pod 自动扩缩器(HPA)来进行自动扩展。在本节中,我们将介绍如何执行这两种操作。
准备就绪
准备以下 YAML 文件,这是一个简单的 Deployment,启动两个nginx容器。同时,一个 NodePort 服务通过 TCP 暴露端口30080:
# cat 3-1-1_deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
replicas: 2
selector:
matchLabels:
service : nginx
template:
metadata:
labels:
service : nginx
spec:
containers:
- name: my-container
image: nginx
---
apiVersion: v1
kind: Service
metadata:
name: my-nginx
spec:
ports:
- protocol: TCP
port: 80
nodePort: 30080
type: NodePort
selector:
service: nginx
NodePort会绑定到所有 Kubernetes 节点(端口范围:30000至32767);因此,请确保NodePort没有被其他进程占用。
让我们使用kubectl来创建前述配置文件所用的资源:
// create deployment and service
# kubectl create -f 3-1-1_deployment.yaml
deployment "my-nginx" created
service "my-nginx" created
几秒钟后,我们应该能够看到pods已经被调度并且正在运行:
# kubectl get pods
NAME READY STATUS RESTARTS AGE
my-nginx-6484b5fc4c-9v7dc 1/1 Running 0 7s
my-nginx-6484b5fc4c-krd7p 1/1 Running 0 7s
服务也已启动:
# kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 20d
my-nginx NodePort 10.105.9.153 <none> 80:30080/TCP 59s
如何操作...
假设我们的服务在某个时间点会出现流量高峰。作为一名 DevOps 工程师,你可能希望在高峰时手动扩展服务,然后在流量高峰过后缩减服务。在 Kubernetes 中,我们可以使用kubectl scale命令来实现。或者,我们可以利用 HPA 根据计算资源条件或自定义指标自动进行扩展和缩减。
让我们看看如何在 Kubernetes 中手动和自动执行此操作。
使用kubectl scale命令手动扩展和缩减
假设今天我们想将nginx Pod 的数量从两个扩展到四个:
// kubectl scale --replicas=<expected_replica_num> deployment <deployment_name>
# kubectl scale --replicas=4 deployment my-nginx
deployment "my-nginx" scaled
让我们检查一下现在有多少个pods:
# kubectl get pods
NAME READY STATUS RESTARTS AGE
my-nginx-6484b5fc4c-9v7dc 1/1 Running 0 1m
my-nginx-6484b5fc4c-krd7p 1/1 Running 0 1m
my-nginx-6484b5fc4c-nsvzt 0/1 ContainerCreating 0 2s
my-nginx-6484b5fc4c-v68dr 1/1 Running 0 2s
我们可以发现两个 Pod 被调度了。一个已经在运行,另一个正在创建。最终,如果我们有足够的计算资源,我们将有四个 Pod 在运行。
kubectl scale(也包括kubectl autoscale!)同样支持Replication Controller(RC)和Replica Set(RS)。不过,推荐的方式是使用 Deployment 来部署 Pod。
我们也可以使用相同的kubectl命令进行缩减,只需将replicas参数设置为更低的值:
// kubectl scale –replicas=<expected_replica_num> deployment <deployment_name>
# kubectl scale --replicas=2 deployment my-nginx
deployment "my-nginx" scaled
现在,我们会看到两个 Pod 被调度以终止:
# kubectl get pods
NAME READY STATUS RESTARTS AGE
my-nginx-6484b5fc4c-9v7dc 1/1 Running 0 1m
my-nginx-6484b5fc4c-krd7p 1/1 Running 0 1m
my-nginx-6484b5fc4c-nsvzt 0/1 Terminating 0 23s
my-nginx-6484b5fc4c-v68dr 0/1 Terminating 0 23s
有一个选项,--current-replicas,它指定了期望的当前副本数。如果不匹配,Kubernetes 将不会执行扩展操作,如下所示:
// adding –-current-replicas to precheck the condistion for scaling.
# kubectl scale --current-replicas=3 --replicas=4 deployment my-nginx
error: Expected replicas to be 3, was 2
水平 Pod 自动扩缩器(HPA)
HPA 会定期查询指标来源,并根据获取到的指标由控制器决定是否需要扩展。可以获取的指标有两种:一种来自 Heapster(github.com/kubernetes/heapster),另一种来自 RESTful 客户端访问。在接下来的示例中,我们将展示如何使用 Heapster 监控 Pods 并将指标暴露给 HPA。
首先,Heapster 需要在集群中部署:
如果您正在运行 minikube,请使用minikube addons enable heapster命令在您的集群中启用 heapster。请注意,minikube logs | grep heapster命令也可以用来检查 heapster 的日志。
// at the time we're writing this book, the latest configuration file of heapster in kops is 1.7.0\. Check out https://github.com/kubernetes/kops/tree/master/addons/monitoring-standalone for the latest version when you use it.
# kubectl create -f https://raw.githubusercontent.com/kubernetes/kops/master/addons/monitoring-standalone/v1.7.0.yaml
deployment "heapster" created
service "heapster" created
serviceaccount "heapster" created
clusterrolebinding "heapster" created
rolebinding "heapster-binding" created
检查heapster pods是否已经启动并运行:
# kubectl get pods --all-namespaces | grep heapster
kube-system heapster-56d577b559-dnjvn 2/2 Running 0 26m
kube-system heapster-v1.4.3-6947497b4-jrczl 3/3 Running 0 5d
假设我们紧接着准备就绪部分继续操作,我们将有两个my-nginx Pods 在我们的集群中运行:
# kubectl get pods
NAME READY STATUS RESTARTS AGE
my-nginx-6484b5fc4c-9v7dc 1/1 Running 0 40m
my-nginx-6484b5fc4c-krd7p 1/1 Running 0 40m
然后,我们可以使用kubectl autoscale命令来部署一个 HPA:
# kubectl autoscale deployment my-nginx --cpu-percent=50 --min=2 --max=5
deployment "my-nginx" autoscaled
# cat 3-1-2_hpa.yaml
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: my-nginx
spec:
scaleTargetRef:
kind: Deployment
name: my-nginx
minReplicas: 2
maxReplicas: 5
targetCPUUtilizationPercentage: 50
要检查它是否按预期运行:
// check horizontal pod autoscaler (HPA)
# kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
my-nginx Deployment/my-nginx <unknown> / 50% 2 5 0 3s
我们发现目标显示为未知,副本数为 0。为什么会这样?它作为一个控制循环运行,默认间隔为 30 秒。可能会有延迟,直到它反映出真实的指标。
HPA 的默认同步周期可以通过更改控制管理器中的以下参数来修改:--horizontal-pod-autoscaler-sync-period。
等待几秒钟后,我们将发现当前的指标已经显示出来。目标列中显示的数字表示(current / target)。这意味着当前负载为0%,扩展目标是50%:
# kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
my-nginx Deployment/my-nginx 0% / 50% 2 5 2 48m
// check details of a hpa
# kubectl describe hpa my-nginx
Name: my-nginx
Namespace: default
Labels: <none>
Annotations: <none>
CreationTimestamp: Mon, 15 Jan 2018 22:48:28 -0500
Reference: Deployment/my-nginx
Metrics: ( current / target )
resource cpu on pods (as a percentage of request): 0% (0) / 50%
Min replicas: 2
Max replicas: 5
为了测试 HPA 是否能正确扩展 Pod,我们将手动生成一些负载到my-nginx服务:
// generate the load
# kubectl run -it --rm --restart=Never <pod_name> --image=busybox -- sh -c "while true; do wget -O - -q http://my-nginx; done"
在前面的命令中,我们运行了一个busybox镜像,它允许我们在上面运行简单的命令。我们使用–c参数指定了默认命令,即无限循环,以查询my-nginx服务。
大约一分钟后,您可以看到当前值发生了变化:
// check current value – it's 43% now. not exceeding scaling condition yet.
# kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
my-nginx Deployment/my-nginx 43% / 50% 2 5 2 56m
使用相同的命令,我们可以反复运行不同 Pod 名称的负载。最后,我们看到条件已经满足,它正在将副本数扩展到3,然后是4:
# kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
my-nginx Deployment/my-nginx 73% / 50% 2 5 3 1h
# kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
my-nginx Deployment/my-nginx 87% / 50% 2 5 4 15m
Keeping observing it and deleting some busybox we deployed. It will eventually cool down and scale down without manual operation involved.
# kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
my-nginx Deployment/my-nginx 40% / 50% 2 5 2 27m
我们可以看到,HPA 刚刚将我们的 Pods 从4缩放到2。
它是如何工作的...
请注意,cAdvisor 作为容器资源利用率监控服务,运行在每个节点的 kubelet 内部。我们刚才监控的 CPU 利用率是由 cAdvisor 收集的,并由 Heapster 汇总。Heapster 是一个在集群中运行的服务,用于监控和汇总指标。它从每个 cAdvisor 查询指标。当 HPA 部署后,控制器将持续观察 Heapster 报告的指标,并据此进行扩展和缩减。以下是该过程的示意图:

根据指定的指标,HPA 决定是否需要进行扩展
还有更多…
另外,你还可以使用自定义指标,如 Pod 指标或对象指标,来判断是否该进行扩展或缩减。Kubernetes 还支持多种指标,HPA 会按顺序考虑每一个指标。有关更多示例,请访问kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale。
另见
本教程描述了如何使用部署的扩展选项来更改 Pod 数量。这对于快速扩展和缩减你的应用非常有用。要了解更多关于如何更新容器的信息,请参考以下教程:
-
更新实时容器 在第三章,与容器玩耍
-
确保容器灵活使用 在第三章,与容器玩耍
更新实时容器
对于容器的好处,我们可以通过执行最新镜像来轻松发布新程序,减少环境设置的麻烦。但是,如何在运行中的容器上发布程序呢?在本地管理容器时,我们必须先停止正在运行的容器,然后才能启动使用最新镜像和相同配置的新容器。对于在 Kubernetes 系统中更新程序,有一些简单且高效的方法。一个方法叫做滚动更新,这意味着 Deployment 可以在不造成客户端停机的情况下更新其 Pods。另一个方法叫做重建,它会终止所有 Pods,然后创建一组新的 Pods。在本教程中,我们将演示如何应用这些解决方案。
在 Docker Swarm 中进行滚动更新为了实现零停机时间的应用更新,Docker Swarm 中有类似的管理功能。在 Docker Swarm 中,你可以利用命令 docker service update 配合 --update-delay、--update-parallelism 和 --update-failure-action 标志来进行管理。想要了解更多关于 Docker Swarm 滚动更新的信息,可以访问官方文档:docs.docker.com/engine/swarm/swarm-tutorial/rolling-update/。
准备就绪
在接下来的演示中,我们将更新nginx Pods。请确保所有 Kubernetes 节点和组件都健康运行:
// check components
$ kubectl get cs
// check nodes
$ kubectl get node
此外,为了更好地理解 ReplicaSet 与 Deployment 之间的关系,请查阅第二章中的Deployment API部分,走进 Kubernetes 概念。
为了演示 Kubernetes 系统中容器的更新,我们将创建一个 Deployment,修改其应用配置,然后检查更新机制如何处理。让我们准备好所有资源:
// create a simple nginx Deployment with specified labels
$ kubectl run simple-nginx --image=nginx --port=80 --replicas=5 --labels="project=My-Happy-Web,role=frontend,env=test"
deployment.apps "simple-nginx" created
这个部署是使用5个副本创建的。通过这种方式,我们可以通过多个 Pod 来探索更新流程:
// expose the Deployment, and named the service "nginx-service"
$ kubectl expose deployment simple-nginx --port=8080 --target-port=80 --name="nginx-service"
service "nginx-service" exposed
// For minikube environment only, since Kubernetes is installed in a VM, add Service type as NodePort for accessing outside the VM.
$ kubectl expose deployment simple-nginx --port=8080 --target-port=80 --name="nginx-service" --type=NodePort service "nginx-service" exposed
在部署中附加一个服务有助于模拟客户的真实体验。
如何操作...
开始时,执行以下代码块查看你刚刚创建的 Deployment 及其 ReplicaSet:
$ kubectl describe deployment simple-nginx
Name: simple-nginx
Namespace: default
CreationTimestamp: Fri, 04 May 2018 12:14:21 -0400
Labels: env=test
project=My-Happy-Web
role=frontend
Annotations: deployment.kubernetes.io/revision=1
Selector: env=test,project=My-Happy-Web,role=frontend
Replicas: 5 desired | 5 updated | 5 total | 5 available | 0 unavailable
StrategyType: RollingUpdate MinReadySeconds: 0
RollingUpdateStrategy: 1 max unavailable, 1 max surge
Pod Template:
Labels: env=test
project=My-Happy-Web
role=frontend
Containers:
simple-nginx:
Image: nginx
Port: 80/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: simple-nginx-585f6cddcd (5/5 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 1h deployment-controller Scaled up replica set simple-nginx-585f6cddcd to 5
// rs is the abbreviated resource key of replicaset
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
simple-nginx-585f6cddcd 5 5 5 1h
根据前面的输出,我们知道默认的部署更新策略是滚动更新。而且,随着 Deployment 创建,还会创建一个名为 <Deployment 名称>-<十六进制哈希> 的 ReplicaSet。
接下来,检查当前 Service 端点的内容,以便稍后验证我们的更新:
// record the cluster IP of Service "nginx-service"
$ export SERVICE_URL=$(kubectl get svc | grep nginx-service | awk '{print $3}'):8080
// For minikube environment only, record the VM host IP and port for the service
$ export SERVICE_URL=$(minikube service nginx-service --url)
$ curl $SERVICE_URL | grep "title"
<title>Welcome to nginx!</title>
我们将在 HTML 响应的标题中看到欢迎信息,并且使用原始的 nginx 镜像。
部署更新策略 – 滚动更新
以下将介绍子命令 edit 和 set,用于更新 Deployment 下的容器:
- 首先,让我们使用新的命令更新 Deployment 中的 Pods:
// get into editor mode with the command below
// the flag "--record" is for recording the update
// add the command argument as below and save the change
$ kubectl edit deployment simple-nginx --record
spec:
replicas: 5
...
template:
...
spec:
containers:
- image: nginx
command: - sh
- -c - echo "Happy Programming with Kubernetes!" > /usr/share/nginx/html/index.html && service nginx stop && nginx -g "daemon off;"
imagePullPolicy: Always
...
deployment.extensions "simple-nginx" edited
我们不仅仅是在做更新,我们还记录这个变化。使用 --record 标志,我们将命令行作为修订的标签保留。
- 编辑 Deployment 后,立即检查滚动更新的状态,使用子命令
rollout:
// you may see different output on your screen, but definitely has the last line showing update successfully
$ kubectl rollout status deployment simple-nginx
Waiting for rollout to finish: 4 out of 5 new replicas have been updated...
Waiting for rollout to finish: 4 out of 5 new replicas have been updated...
Waiting for rollout to finish: 4 out of 5 new replicas have been updated...
Waiting for rollout to finish: 4 out of 5 new replicas have been updated...
Waiting for rollout to finish: 1 old replicas are pending termination...
Waiting for rollout to finish: 1 old replicas are pending termination...
Waiting for rollout to finish: 1 old replicas are pending termination...
Waiting for rollout to finish: 4 of 5 updated replicas are available...
deployment "simple-nginx" successfully rolled out
你可能会看到若干 Waiting for … 行,正如前面的代码所示。它们是标准输出,显示更新的状态。
- 对于整个更新过程,检查 Deployment 的详细信息以列出其事件:
// describe the Deployment again
$ kubectl describe deployment simple-nginx
Name: simple-nginx
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 1h deployment-controller Scaled up replica set simple-nginx-585f6cddcd to 5
Normal ScalingReplicaSet 1h deployment-controller Scaled up replica set simple-nginx-694f94f77d to 1
Normal ScalingReplicaSet 1h deployment-controller Scaled down replica set simple-nginx-585f6cddcd to 4
Normal ScalingReplicaSet 1h deployment-controller Scaled up replica set simple-nginx-694f94f77d to 2
Normal ScalingReplicaSet 1h deployment-controller Scaled down replica set simple-nginx-585f6cddcd to 3
Normal ScalingReplicaSet 1h deployment-controller Scaled up replica set simple-nginx-694f94f77d to 3
Normal ScalingReplicaSet 1h deployment-controller Scaled down replica set simple-nginx-585f6cddcd to 2
Normal ScalingReplicaSet 1h deployment-controller Scaled up replica set simple-nginx-694f94f77d to 4
Normal ScalingReplicaSet 1h deployment-controller Scaled down replica set simple-nginx-585f6cddcd to
Normal ScalingReplicaSet 1h deployment-controller Scaled up replica set simple-nginx-694f94f77d to 5
Normal ScalingReplicaSet 1h deployment-controller (combined from similar events): Scaled down replica set simple-nginx-585f6cddcd to 0
如你所见,simple-nginx 部署中创建了一个新的 replica set simple-nginx-694f94f77d。每当新的 ReplicaSet 成功扩展一个 Pod 时,旧的 ReplicaSet 会将一个 Pod 缩减。当新的 ReplicaSet 达到原定的 Pod 数量(如,5 个 Pod),且旧的 ReplicaSet 变为零个 Pod 时,扩展过程完成。
- 继续检查此次更新的新的 ReplicaSet 和现有的 Service:
// look at the new ReplicaSet in detail, you will find it copied the labels of the old one
$ kubectl describe rs simple-nginx-694f94f77d
Name: simple-nginx-694f94f77d
Namespace: default
Selector: env=test,pod-template-hash=2509509338,project=My-Happy-Web,role=frontend Labels: env=test
pod-template-hash=2509509338
project=My-Happy-Web
role=frontend
...
// send request to the same endpoint of Service.
$ curl $SERVICE_URL
Happy Programming with Kubernetes!
-
让我们做另一次更新!这次,使用子命令
set修改 Pod 的特定配置。 -
要为 Deployment 中的某些容器设置新镜像,子命令的格式如下:
kubectl set image deployment <Deployment 名称> <Container 名称>=<镜像名称>:
// change the image version with the subcommand "set"
// when describing the deployment, we can know that the container name is the same as the name of the Deployment
// record this change as well
$ kubectl set image deployment simple-nginx simple-nginx=nginx:stable --record
deployment.apps "simple-nginx" image updated
"set" 子命令还能帮助配置哪些内容?
set 子命令有助于定义应用程序的配置。直到版本 1.9,CLI 使用 set 可以分配或更新以下资源:
| set 后的子命令 | 作用资源 | 更新项 |
|---|---|---|
env |
Pod | 环境变量 |
image |
Pod | 容器镜像 |
resources |
Pod | 计算资源需求或限制 |
selector |
任何资源 | 选择器 |
serviceaccount |
任何资源 | ServiceAccount |
subject |
RoleBinding 或 ClusterRoleBinding | 用户、组或 ServiceAccount |
- 现在,检查更新是否已完成,以及镜像是否已更改:
// check update status by rollout
$ kubectl rollout status deployment simple-nginx
...
deployment "simple-nginx" successfully rolled out
// check the image of Pod in simple-nginx
$ kubectl describe deployment simple-nginx
Name: simple-nginx
...
Pod Template:
Labels: env=test
project=My-Happy-Web
role=frontend
Containers:
simple-nginx:
Image: nginx:stable
Port: 80/TCP
Host Port: 0/TCP
...
- 你还可以查看 ReplicaSets。应该会有另一个负责管理 Pods 的 ReplicaSet:
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
simple-nginx-585f6cddcd 0 0 0 1h
simple-nginx-694f94f77d 0 0 0 1h
simple-nginx-b549cc75c 5 5 5 1h
回滚更新
Kubernetes 系统会记录每次 Deployment 的更新:
- 我们可以使用子命令
rollout列出所有修订:
// check the rollout history
$ kubectl rollout history deployment simple-nginx
deployments "simple-nginx"
REVISION CHANGE-CAUSE
1 <none>
2 kubectl edit deployment simple-nginx --record=true
3 kubectl set image deployment simple-nginx simple-nginx=nginx:stable --record=true
对于simple-nginx部署,您将获得三个修订版本,就像前面的行一样。对于 Kubernetes 部署,每个修订版本都有一个匹配的ReplicaSet,并代表执行更新命令的一个阶段。第一个修订版本是simple-nginx的初始状态。虽然没有命令标签进行指示,但 Kubernetes 将其创建视为第一个版本。不过,在创建部署时,您仍然可以记录该命令。
-
在子命令
create或run后添加--record标志。 -
通过修订版本,我们可以轻松恢复更改,即回滚更新。使用以下命令回滚到先前的修订版本:
// let's jump back to initial Deployment!
// with flag --to-revision, we can specify which revision for rollback processing
$ kubectl rollout undo deployment simple-nginx --to-revision=1
deployment.apps "simple-nginx"
// check if the rollback update is finished
$ kubectl rollout status deployment simple-nginx
...
deployment "simple-nginx" successfully rolled out
// take a look at ReplicaSets, you will find that the old ReplicaSet takes charge of the business now
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
simple-nginx-585f6cddcd 5 5 5 4h
simple-nginx-694f94f77d 0 0 0 4h
simple-nginx-b549cc75c 0 0 0 3h
// go ahead and check the nginx webpage or the details of Deployment
$ curl $SERVICE_URL
$ kubectl describe deployment simple-nginx
- 如果没有指定修订版本号,回滚过程将直接跳回到先前版本:
// just go back to previous status
$ kubectl rollout undo deployment simple-nginx
deployment.apps "simple-nginx"
// look at the ReplicaSets agin, now the latest one takes the job again
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
simple-nginx-585f6cddcd 0 0 0 4h
simple-nginx-694f94f77d 0 0 0 4h
simple-nginx-b549cc75c 5 5 5 4h
部署更新策略 – recreate
接下来,我们将介绍另一种更新策略,recreate,用于部署。虽然没有子命令或标志来创建recreate策略的部署,但用户可以通过覆盖默认元素并指定相应的配置来实现此创建:
// create a new Deployment, and override the update strategy.
$ kubectl run recreate-nginx --image=nginx --port=80 --replicas=5 --overrides='{"apiVersion": "apps/v1", "spec": {"strategy": {"type": "Recreate"}}}'
deployment.apps "recreate-nginx" created
// verify our new Deployment
$ kubectl describe deployment recreate-nginx
Name: recreate-nginx
Namespace: default
CreationTimestamp: Sat, 05 May 2018 18:17:07 -0400
Labels: run=recreate-nginx
Annotations: deployment.kubernetes.io/revision=1
Selector: run=recreate-nginx
Replicas: 5 desired | 5 updated | 5 total | 0 available | 5 unavailable
StrategyType: Recreate ...
根据我们的理解,recreate模式适用于正在开发中的应用程序。在recreate模式下,Kubernetes 会将当前的 ReplicaSet 缩放至零个 Pod,然后创建一个具有所需 Pod 数量的新 ReplicaSet。因此,recreate比滚动更新(rolling-update)拥有更短的整体更新时间,因为它只是简单地缩放 ReplicaSet,一次性完成。由于开发中的部署不需要考虑用户体验,因此在更新过程中出现停机是可以接受的,同时可以享受更快的更新速度:
// try to update recreate-strategy Deployment
$ kubectl set image deployment recreate-nginx recreate-nginx=nginx:stable
deployment.apps "recreate-nginx" image updated
// check both the rollout status and the events of Deployment
$ kubectl rollout status deployment recreate-nginx
$ kubectl describe deployment recreate-nginx
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 3h deployment-controller Scaled up replica set recreate-nginx-9d5b69986 to 5
Normal ScalingReplicaSet 2h deployment-controller Scaled down replica set recreate-nginx-9d5b69986 to 0
Normal ScalingReplicaSet 2h deployment-controller Scaled up replica set recreate-nginx-674d7f9c7f to 5
它是如何工作的...
滚动更新作用于部署中的 ReplicaSet 单元。其效果是创建一个新的 ReplicaSet 来替代旧的 ReplicaSet。然后,新 ReplicaSet 将被扩展以满足所需的 Pod 数量,同时旧的 ReplicaSet 将缩减以终止其中的所有 Pod。新 ReplicaSet 中的 Pod 会附加到原始标签。因此,如果有任何服务暴露此部署,它将直接接管新创建的 Pods。
一个经验丰富的 Kubernetes 用户可能知道,资源 ReplicationController 也可以进行滚动更新。那么,ReplicationController 和部署之间的滚动更新有何不同呢?扩缩容处理使用的是 ReplicationController 与kubectl等客户端的组合。新的 ReplicationController 将被创建以替代旧的 ReplicationController。客户端在替换过程中不会感到任何中断,因为服务始终在 ReplicationController 前面。然而,开发者很难回滚到之前的 ReplicationController(因为它们已被删除),因为没有内置的机制来记录更新历史。
此外,如果在滚动更新过程中客户端连接被断开,滚动更新可能会失败。最重要的是,带有 ReplicaSet 的 Deployment 是比 ReplicationController 或独立 ReplicaSet 更推荐的部署资源。
在密切关注部署更新历史时,请注意它并不总是按顺序列出。添加修订版本的算法可以通过以下要点来说明:
-
以最后一个修订版本的编号为N
-
当新的滚动更新到来时,它将变为N+1
-
回滚到特定的修订版本号X,X将被删除,且它将变为N+1
-
回滚到上一个版本,即N-1,然后N-1将被删除,且它将变为N+1
通过这种修订管理,不会有过时或重叠的更新占用滚动历史记录。
还有更多...
考虑到部署更新,是构建 CI/CD(持续集成与持续交付)流水线的一个良好步骤。对于更常见的用法,开发者通常不会使用命令行来更新部署。相反,他们可能会选择从 CI/CD 平台发起 API 调用,或者从先前的配置文件中进行更新。以下是与子命令apply一起工作的示例:
// A simple nginx Kubernetes configuration file
$ cat my-update-nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-update-nginx
spec:
replicas: 5
selector:
matchLabels:
run: simple-nginx
template:
metadata:
labels:
run: simple-nginx
spec:
containers:
- name: simple-nginx
image: nginx
ports:
- containerPort: 80
// create the Deployment by file and recording the command in the revision tag
$ kubectl create -f my-update-nginx.yaml --record
deployment.apps "my-update-nginx" created
作为演示,将容器镜像从nginx修改为nginx:stable(你可以查看代码包my-update-nginx-updated.yaml中的修改)。然后,我们可以使用更改后的文件通过子命令apply进行更新:
$ kubectl apply -f my-update-nginx-updated.yaml --record
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
deployment.apps "my-update-nginx" configured
// check the update revisions and status
$ kubectl rollout history deployment my-update-nginx
deployments "my-update-nginx"
REVISION CHANGE-CAUSE
1 kubectl create --filename=my-update-nginx.yaml --record=true
2 kubectl apply --filename=my-update-nginx-updated.yaml --record=true
$ kubectl rollout status deployment my-update-nginx
deployment "my-update-nginx" successfully rolled out
现在,你可以学习另一种更新你的部署的方式。
深入了解 Deployment 的滚动更新时,我们可以利用一些参数来进行更新:
-
minReadySeconds:当 Pod 被认为已准备好后,系统仍然会等待一段时间才能进行下一步操作。这个时间段就是最小准备时间,这在等待应用完成后配置时非常有用。 -
maxUnavailable:更新期间可以不可用的最大 Pod 数。值可以是百分比(默认是 25%)或整数。如果maxSurge的值为0,表示不能容忍 Pod 数量超过期望数,那么maxUnavailable的值不能为0。 -
maxSurge:更新期间可以创建的超出期望 ReplicaSet 数量的最大 Pod 数。值可以是百分比(默认是 25%)或整数。如果maxUnavailable的值为0,表示服务中的 Pod 数应该始终符合期望值,那么maxSurge的值不能为0。
基于代码包中的配置文件my-update-nginx-advanced.yaml,你可以自己尝试操作这些参数,看看能否感受到它们的实际效果。
另见
你可以继续学习以下的配方,深入了解如何高效地部署 Kubernetes 资源:
-
扩展你的容器
-
使用配置文件
-
第五章中的将单体应用迁移到微服务,与 Jenkins 集成,使用私有 Docker 镜像库和设置持续交付管道的配方,构建持续交付管道
转发容器端口
在之前的章节中,你已经学会了如何使用 Kubernetes 服务来内部和外部转发容器端口。现在,是时候更进一步,了解它是如何工作的。
Kubernetes 中有四种网络模型,我们将在以下章节中详细探讨:
-
容器之间的通信
-
Pod 之间的通信
-
Pod 与服务的通信
-
外部到内部的通信
准备工作
在深入了解 Kubernetes 网络之前,我们先研究 Docker 的网络,以便理解基本概念。每个容器将拥有一个网络命名空间,具有自己的路由表和路由策略。默认情况下,网络桥接docker0连接物理网络接口和容器的虚拟网络接口,虚拟网络接口则是容器网络命名空间和主机网络命名空间之间的双向连接线。因此,单个容器有一对虚拟网络接口:容器内的以太网接口(eth0)和主机上的虚拟以太网接口(veth-)。
网络结构可以如以下图所示:

主机上的容器网络接口
什么是网络命名空间?网络命名空间是 Linux 内核提供的一种技术。通过这个特性,操作系统可以通过将网络能力分离为独立资源来实现网络虚拟化。每个网络命名空间都有自己的 iptables 设置和网络设备。
如何操作...
一个 Pod 包含一个或多个容器,这些容器运行在同一主机上。每个 Pod 在叠加网络上都有自己的 IP 地址;Pod 内的所有容器互相之间视为同一主机上的容器。Pod 内的容器将几乎同时创建、部署和删除。我们将展示容器、Pod 和服务之间的四种通信模型。
容器之间的通信
在这种场景中,我们将重点讨论单一 Pod 内容器之间的通信:
- 让我们在一个 Pod 中创建两个容器:一个 nginx Web 应用和一个检查 localhost 上
80端口的 CentOS 容器:
// configuration file of creating two containers within a pod
$ cat two-container-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: two-container
spec:
containers:
- name: web
image: nginx
ports:
- containerPort: 80
hostPort: 80
- name: centos
image: centos
command: ["/bin/sh", "-c", "while : ;do curl http://localhost:80/; sleep 30; done"]
// create the pod
$ kubectl create -f two-container-pod.yaml
pod "two-container" created
// check the status of the newly-created Pod
$ kubectl get pod two-container
NAME READY STATUS RESTARTS AGE
two-container 2/2 Running 0 5s
我们看到READY列中的计数变为2/2,因为这个 Pod 内有两个容器。
- 使用
kubectl describe命令,我们可以查看 Pod 的详细信息:
$ kubectl describe pod two-container
Name: two-container
Namespace: default
Node: ubuntu02/192.168.122.102
Start Time: Sat, 05 May 2018 18:28:22 -0400
Labels: <none>
Annotations: <none>
Status: Running
IP: 192.168.79.198 Containers:
web:
Container ID: docker://e832d294f176f643d604445096439d485d94780faf60eab7ae5d3849cbf15d75
...
centos:
Container ID: docker://9e35275934c1acdcfac4017963dc046f9517a8c1fc972df56ca37e69d7389a72
...
我们可以看到 Pod 运行在节点ubuntu02上,且其 IP 地址是192.168.79.198。
- 此外,我们可能会发现 Centos 容器能够访问 localhost 上的
nginx:
$ kubectl logs two-container centos | grep "title"
<title>Welcome to nginx!</title>
...
- 让我们登录到节点
ubuntu02,检查这两个容器的网络设置:
// list containers of the Pod
$ docker ps | grep "two-container"
9e35275934c1 centos "/bin/sh -c 'while..." 11 hours ago Up 11 hours k8s_centos_two-container_default_113e727f-f440-11e7-ac3f-525400a9d353_0
e832d294f176 nginx "nginx -g 'daemon ..." 11 hours ago Up 11 hours k8s_web_two-container_default_113e727f-f440-11e7-ac3f-525400a9d353_0
9b3e9caf5149 gcr.io/google_containers/pause-amd64:3.1 "/pause" 11 hours ago Up 11 hours k8s_POD_two-container_default_113e727f-f440-11e7-ac3f-525400a9d353_0
现在,我们知道创建的两个容器是9e35275934c1和e832d294f176。另一方面,还有一个容器9b3e9caf5149,是由 Kubernetes 创建的,使用 Docker 镜像gcr.io/google_containers/pause-amd64。我们稍后会介绍它。之后,我们可以通过命令docker inspect对容器进行详细检查,并通过将命令jq(stedolan.github.io/jq/)作为管道添加,可以解析输出信息,仅显示网络设置。
- 看一下同一 Pod 中的两个容器:
// inspect the nginx container, and use jq to parse it
$ docker inspect e832d294f176 | jq '.[]| {NetworkMode: .HostConfig.NetworkMode, NetworkSettings: .NetworkSettings}'
{
"NetworkMode": "container:9b3e9caf5149ffb0ec14c1ffc36f94b2dd55b223d0d20e4d48c4e33228103723",
"NetworkSettings": {
"Bridge": "",
"SandboxID": "",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": {},
"SandboxKey": "",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "",
"Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "",
"IPPrefixLen": 0,
"IPv6Gateway": "",
"MacAddress": "",
"Networks": {}
}
}
// then inspect the centos one
$ docker inspect 9e35275934c1 | jq '.[]| {NetworkMode: .HostConfig.NetworkMode, NetworkSettings: .NetworkSettings}'
{
"NetworkMode": "container:9b3e9caf5149ffb0ec14c1ffc36f94b2dd55b223d0d20e4d48c4e33228103723",
...
我们可以看到两个容器具有相同的网络设置;网络模式设置为映射容器模式,其他配置被清理。网络桥接容器是container:9b3e9caf5149ffb0ec14c1ffc36f94b2dd55b223d0d20e4d48c4e33228103723。这个容器是什么?它是 Kubernetes 创建的,容器 ID 为9b3e9caf5149,镜像为gcr.io/google_containers/pause-amd64。
“暂停”容器做什么?正如其名称所示,这个容器什么也不做,只是“暂停”。然而,它保留了 Pod 的网络设置和 Linux 网络命名空间。每次容器关闭并重新启动时,网络配置仍然保持不变,无需重新创建,因为“暂停”容器保存了它。你可以在github.com/kubernetes/kubernetes/tree/master/build/pause查看其代码和 Dockerfile,了解更多信息。
“暂停”容器是一个网络容器,当 Pod 创建时会生成它。
它被创建并用于处理 Pod 网络的路由。然后,两个容器将与暂停容器共享网络命名空间;这就是它们彼此视为 localhost 的原因。
在 Docker 中创建网络容器:你可以轻松地将容器转换为网络容器,与另一个容器共享网络命名空间。使用命令行:$ docker run --network=container:<CONTAINER_ID 或 CONTAINER_NAME> [其他选项]。然后,你就能够启动一个使用指定容器网络命名空间的容器。
Pod 到 Pod 的通信
如前所述,Pod 中的容器共享相同的网络命名空间。Pod 是 Kubernetes 中的基本计算单元。Kubernetes 为 Pod 分配一个 IP,在其网络中,每个 Pod 都可以通过虚拟 IP 看到其他 Pod。谈到 Pod 之间的通信时,我们可以分为两种情况:在同一节点内通信的 Pod,或者跨节点通信的 Pod。对于单节点中的 Pod,由于它们有独立的 IP,它们的传输可以通过桥接完成,类似于 Docker 节点中的容器。然而,对于跨节点的 Pod 之间的通信,当 Pod 没有主机信息(主机 IP)时,如何进行包路由?
Kubernetes 使用 CNI 来处理集群网络。CNI 是一个用于管理连接容器的框架,负责为容器分配或删除网络资源。Kubernetes 将 CNI 作为插件,用户可以根据需要选择 CNI 的实现。常见的 CNI 类型包括以下几种:
-
覆盖网络:采用数据包封装技术。每个数据都被包装上主机 IP,因此可以在互联网上进行路由。一个例子是 flannel (
github.com/coreos/flannel)。 -
L3 网关:容器之间的传输首先经过一个网关节点。该网关会维护路由表,将容器子网与主机 IP 映射。一个例子是 Project Calico (
www.projectcalico.org/)。 -
L2 邻接:发生在 L2 交换层。在以太网中,两个节点如果数据包可以直接从源节点传输到目标节点,而无需经过其他节点,则认为它们是邻接的。一个例子是 Cisco ACI (
www.cisco.com/c/en/us/td/docs/switches/datacenter/aci/apic/sw/kb/b_Kubernetes_Integration_with_ACI.html)。
每种 CNI 类型都有优缺点。前面列出的类型具有更好的可扩展性,但性能较差,而后者则具有更短的延迟,但需要复杂且定制化的设置。有些 CNI 在不同模式下涵盖了所有三种类型,例如 Contiv (github.com/contiv/netplugin)。您可以通过查看其规格了解更多有关 CNI 的信息,网址为:github.com/containernetworking/cni。此外,还可以查看 Kubernetes 官方网站上的 CNI 列表,尝试这些 CNI:kubernetes.io/docs/concepts/cluster-administration/networking/#how-to-achieve-this。
在介绍了 Pod 之间数据包传输的基础知识后,我们将继续为您带来一个 Kubernetes API,NetworkPolicy,它提供了 Pod 之间通信的高级管理功能。
与 NetworkPolicy 一起工作
作为 Kubernetes 的资源,NetworkPolicy 使用标签选择器来配置 Pod 的防火墙,作用于基础设施层面。如果没有指定 NetworkPolicy,集群中的任何 Pod 默认都可以相互通信。另一方面,一旦将带有规则的 NetworkPolicy 附加到 Pod,无论是针对入口流量还是出口流量,或两者兼有,所有不符合规则的流量都将被阻止。
在演示如何构建 NetworkPolicy 之前,我们需要确保 Kubernetes 集群中的网络插件支持它。有几种 CNI 支持 NetworkPolicy:Calico、Contive、Romana (github.com/romana/kube),Weave Net (github.com/weaveworks/weave),Trireme (github.com/aporeto-inc/trireme-kubernetes),等等。
在 minikube 中启用 CNI 并支持 NetworkPolicy 作为网络插件。在使用 minikube 时,用户不需要特别附加 CNI,因为它设计为单节点本地 Kubernetes。但是,为了启用 NetworkPolicy 的功能,必须启动一个支持 NetworkPolicy 的 CNI。需要注意的是,在配置 minikube 时,不同的 CNI 实现可能会有不同的配置选项和步骤。以下步骤展示了如何使用 CNI 和 Calico 启动 minikube:
-
我们参考这个问题
github.com/projectcalico/calico/issues/1013#issuecomment-325689943来进行这些构建步骤。 -
这里使用的 minikube 是最新版本 0.24.1。
-
重启你的 minikube:
minikube start --network-plugin=cni \--host-only-cidr 172.17.17.1/24 \ --extra-config=kubelet.PodCIDR=192.168.0.0/16 \ --extra-config=proxy.ClusterCIDR=192.168.0.0/16 \--extra-config=controller-manager.ClusterCIDR=192.168.0.0/16。 -
使用代码包中的配置文件“minikube-calico.yaml”创建 Calico:
kubectl create -f minikube-calico.yaml。
为了说明 NetworkPolicy 的功能,我们将创建一个 Pod 并将其暴露为服务,然后在 Pod 上附加一个 NetworkPolicy,看看会发生什么:
// start a pod of our favourite example, nginx
$ kubectl run nginx-pod --image=nginx --port=80 --restart=Never
pod "nginx-pod" created
//expose the pod as a service listening on port 8080
$ kubectl expose pod nginx-pod --port=8080 --target-port=80
service "nginx-pod" exposed
// check the service IP
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 1h
nginx-pod ClusterIP 10.102.153.182 <none> 8080/TCP 1m
现在,我们可以继续检查来自简单部署busybox的 Pod 连接,使用wget命令并带上--spider标志来验证端点是否存在:
// check the accessibility of the service
// create busybox and open standard input and independent terminal by flag "i" and "t", similar to docker command
$ kubectl run busybox -it --image=busybox /bin/sh
If you don't see a command prompt, try pressing enter.
/ # wget --spider 10.102.153.182:8080
Connecting to 10.102.153.182:8080 (10.102.153.182:8080)
如前面的结果所示,我们知道nginx服务可以在没有任何限制的情况下访问。接下来,我们将运行一个NetworkPolicy,限制只有标签为<test: inbound>的 Pod 才能访问nginx服务:
// a configuration file defining NetworkPolicy of pod nginx-pod
$ cat networkpolicy.yaml
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: nginx-networkpolicy
spec:
podSelector:
matchLabels:
run: nginx-pod
ingress:
- from:
- podSelector:
matchLabels:
test: inbound
如你所见,在 NetworkPolicy 的 spec 中,它被配置为应用于标签为<run: nginx-pod>的 Pod,而我们在pod nginx-pod上正好有这个标签。此外,策略中还附加了一个入站规则,表示只有具有特定标签的 Pod 才能访问nginx-pod:
// create the NetworkPolicy
$ kubectl create -f networkpolicy.yaml
networkpolicy.networking.k8s.io "nginx-networkpolicy" created
// check the details of NetworkPolicy
$ kubectl describe networkpolicy nginx-networkpolicy
Name: nginx-networkpolicy
Namespace: default
Created on: 2018-05-05 18:36:56 -0400 EDT
Labels: <none>
Annotations: <none>
Spec:
PodSelector: run=nginx-pod
Allowing ingress traffic:
To Port: <any> (traffic allowed to all ports)
From PodSelector: test=inbound
Allowing egress traffic:
<none> (Selected pods are isolated for egress connectivity)
Policy Types: Ingress
很好,一切看起来如我们所预期的那样。接下来,检查之前的busybox Pod 上的相同服务端点:
// if you turned off the terminal, resume it with the subcommand attach
$ kubectl attach busybox-598b87455b-s2mfq -c busybox -i -t
// we add flag to specify timeout interval, otherwise it will just keep hanging on wget
/ # wget --spider 10.102.153.182:8080 --timeout=3
wget: download timed out
如预期的那样,附加 NetworkPolicy 后,我们无法访问nginx-pod服务。nginx-pod只能被标签为<test: inbound>的 Pod 访问:
// verify the connection by yourself with new busybox
$ kubectl run busybox-labelled --labels="test=inbound" -it --image=busybox /bin/sh
在第二章,《Kubernetes 概念概览》中,通过《与标签和选择器的工作》一节,了解标签和选择器的概念。
在这种情况下,您已学习如何通过 Pod 选择器创建一个带有入口限制的 NetworkPolicy。仍然,您可能希望在 Pod 上进行其他设置:
-
出口限制:出口规则可以通过
.spec.egress进行设置,其配置方式与入口类似。 -
端口限制:每个入口和出口规则可以指明要接受或阻止的端口和端口协议。端口配置可以通过
.spec.ingress.ports或.spec.egress.ports进行设置。 -
命名空间选择器:我们还可以对特定的命名空间设置限制。例如,系统守护进程的 Pods 可能只允许访问
kube-system命名空间中的其他 Pod。命名空间选择器可以通过.spec.ingress.from.namespaceSelector或.spec.egress.to.namespaceSelector进行应用。 -
IP 封锁:一种更定制化的配置是对特定的 CIDR 范围设置规则,这与我们在 iptables 中使用的想法类似。我们可以通过
.spec.ingress.from.ipBlock或.spec.egress.to.ipBlock来利用此配置。
建议查阅 API 文档中的更多详细信息:kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#networkpolicyspec-v1-networking。此外,我们还将展示一些有趣的设置,以满足常见的情况:
-
应用于所有 Pod:通过将
.spec.podSelector设置为空值,可以轻松将 NetworkPolicy 推送到每个 Pod。 -
允许所有流量:我们可以通过将
.spec.ingress设置为空值(空数组)来允许所有传入流量;同样,通过将.spec.egress设置为空值,可以不加任何限制地允许所有的传出流量。 -
拒绝所有流量:我们可以通过简单地指明 NetworkPolicy 的类型而不设置任何规则,来拒绝所有的传入或传出流量。NetworkPolicy 的类型可以在
.spec.policyTypes中设置。同时,不设置.spec.ingress或.spec.egress。
请检查代码包中的示例文件networkpolicy-allow-all.yaml和networkpolicy-deny-all.yaml。
Pod 与 Service 的通信
在普通情况下,Pods 可能会意外停止。然后,Pod 的 IP 可能会发生变化。当我们暴露一个 Pod 或 Deployment 的端口时,我们会创建一个 Kubernetes Service,它充当代理或负载均衡器。Kubernetes 会创建一个虚拟 IP,接收来自客户端的请求并将流量代理到 Service 中的 Pods。让我们回顾一下如何操作:
- 首先,我们将创建一个 Deployment 并将其暴露为一个 Service:
$ cat nodeport-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodeport-deploy
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: my-nginx
image: nginx
---
apiVersion: v1
kind: Service
metadata:
name: nodeport-svc
spec:
type: NodePort
selector:
app: nginx
ports:
- protocol: TCP
port: 8080
targetPort: 80
$ kubectl create -f nodeport-deployment.yaml
deployment.apps "nodeport-deploy" created
service "nodeport-svc" created
- 此时,请使用子命令
describe检查 Service 的详细信息:
$ kubectl describe service nodeport-svc
Name: nodeport-svc
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=nginx
Type: NodePort
IP: 10.101.160.245
Port: <unset> 8080/TCP
TargetPort: 80/TCP
NodePort: <unset> 30615/TCP
Endpoints: 192.168.80.5:80,192.168.80.6:80
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
该服务的虚拟 IP 是10.101.160.245,暴露了端口8080。然后,服务将流量分发到两个端点192.168.80.5:80和192.168.80.6:80。此外,由于服务是以NodePort类型创建的,客户端可以通过每个 Kubernetes 节点的<NODE_IP>:30615访问此服务。正如我们在第二章《走进 Kubernetes 概念》中对与服务一起工作的理解,实际上是 Kubernetes 守护进程kube-proxy帮助在每个节点上维护和更新路由策略。
- 继续检查任何 Kubernetes 节点上的
iptable:
注意!如果你在 minikube 环境中,应该使用命令minikube ssh进入节点。
// Take a look at following marked "Chain"
$ sudo iptables -t nat -nL
...
Chain KUBE-NODEPORTS (1 references)
target prot opt source destination
KUBE-MARK-MASQ tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/nodeport-svc: */ tcp dpt:30615
KUBE-SVC-GFPAJ7EGCNM4QF4H tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/nodeport-svc: */ tcp dpt:30615
...
Chain KUBE-SEP-DIS6NYZTQKZ5ALQS (1 references)
target prot opt source destination
KUBE-MARK-MASQ all -- 192.168.80.6 0.0.0.0/0 /* default/nodeport-svc: */
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/nodeport-svc: */ tcp to:192.168.80.6:80
...
Chain KUBE-SEP-TC6HXYYMMLGUSFNZ (1 references)
target prot opt source destination
KUBE-MARK-MASQ all -- 192.168.80.5 0.0.0.0/0 /* default/nodeport-svc: */
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/nodeport-svc: */ tcp to:192.168.80.5:80
Chain KUBE-SERVICES (2 references)
target prot opt source destination
...
KUBE-SVC-GFPAJ7EGCNM4QF4H tcp -- 0.0.0.0/0 10.101.160.245 /* default/nodeport-svc: cluster IP */ tcp dpt:8080
...
KUBE-NODEPORTS all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL
...
Chain KUBE-SVC-GFPAJ7EGCNM4QF4H (2 references)
target prot opt source destination
KUBE-SEP-TC6HXYYMMLGUSFNZ all -- 0.0.0.0/0 0.0.0.0/0 /* default/nodeport-svc: */ statistic mode random probability 0.50000000000
KUBE-SEP-DIS6NYZTQKZ5ALQS all -- 0.0.0.0/0 0.0.0.0/0 /* default/nodeport-svc: */
...
会有很多规则显示出来。为了专注于与服务nodeport-svc相关的策略,请按照以下步骤检查所有规则。屏幕上的输出可能不会按预期的顺序列出:
-
在链
KUBE-NODEPORTS下查找带有注释nodeport-svc的目标。一个目标将以KUBE-SVC-为前缀命名。在前面的输出中,它是名为KUBE-SVC-GFPAJ7EGCNM4QF4H的目标。与另一个目标KUBE-MARK-MASQ一起,它们负责将端口30615上的流量转发到服务。 -
在
Chain KUBE-SERVICES下查找名为KUBE-SVC-XXX的特定目标。在这个案例中,它是名为KUBE-SVC-GFPAJ7EGCNM4QF4H的目标,规定允许来自“任何地方”的流量到达nodeport-svc的端点10.160.245:8080。 -
在特定的
Chain KUBE-SVC-XXX下查找目标。在此案例中,它是Chain KUBE-SVC-GFPAJ7EGCNM4QF4H。在服务链下,您将根据相应的 Pods 有多个目标,目标的前缀为KUBE-SEP-。在前面的输出中,它们是KUBE-SEP-TC6HXYYMMLGUSFNZ和KUBE-SEP-DIS6NYZTQKZ5ALQS。 -
在特定的
Chain KUBE-SEP-YYY下查找目标。在这个案例中,两个需要查看的链是Chain KUBE-SEP-TC6HXYYMMLGUSFNZ和Chain KUBE-SEP-DIS6NYZTQKZ5ALQS。它们各自包含两个目标,KUBE-MARK-MASQ和DNAT,用于处理从“任何地方”到 Pod 端点192.168.80.5:80或192.168.80.6:80的进出流量。
这里的一个关键点是,服务目标KUBE-SVC-GFPAJ7EGCNM4QF4H将其集群 IP 暴露到外部世界,并将流量转发到链KUBE-SEP-TC6HXYYMMLGUSFNZ和KUBE-SEP-DIS6NYZTQKZ5ALQS,并以随机概率 0.5 作为统计模式。两个链都有 DNAT 目标,负责将数据包的目标 IP 更改为特定 Pod 所在的私有子网 IP。
外部到内部的通信
为了在 Kubernetes 中发布应用程序,我们可以利用 Kubernetes Service,类型为 NodePort 或 LoadBalancer,或者 Kubernetes Ingress。对于 NodePort 服务,如前一节介绍的那样,节点的端口号会与 Service 配对。如下图所示,节点 1 和节点 2 上的端口 30361 指向 Service A,该 Service 会将流量调度到 Pod1 和一个具有静态概率的 Pod。
LoadBalancer Service,正如你在 第二章“与 Services 一起工作”中学到的内容,包含了 NodePort 的配置。此外,LoadBalancer Service 可以与外部负载均衡器一起工作,为用户提供在云基础设施与 Kubernetes 资源之间集成负载均衡过程的功能,例如 healthCheckNodePort 和 externalTrafficPolicy 的设置。下图中的 Service B 是一个 LoadBalancer Service。在内部,Service B 和 Service A 的工作方式相同,依赖 iptables 将数据包重定向到 Pod;在外部,云负载均衡器并不认识 Pod 或容器,它只是根据节点的数量分配流量。无论选择哪个节点来接收请求,它依然能将数据包传递到正确的 Pod。

Kubernetes 的 NodePort 类型和 LoadBalancer 类型 Services
使用 Ingress
在走访 Kubernetes 网络的过程中,用户会意识到每个 Pod 和 Service 都有自己的私有 IP 和相应的端口来监听请求。在实际应用中,开发人员可能会为内部客户端提供服务的端点、私有 IP 或 Kubernetes DNS 名称;或者,开发人员可能会通过 NodePort 或 LoadBalancer 类型将 Services 暴露给外部。尽管 Service 的端点比 Pod 更稳定,但 Services 是单独提供的,客户端应该记录这些 IP 地址,尽管这些 IP 对它们没有太大意义。在本节中,我们将介绍 Ingress,一种可以使你的 Services 作为一个整体工作的资源。更重要的是,我们可以轻松地将服务集打包成一个 API 服务器,同时设置 Ingress 规则来识别不同的 URL,然后 Ingress 控制器根据这些规则将请求转发到特定的 Services。
在我们尝试 Kubernetes Ingress 之前,我们应在集群中创建一个 ingress controller。与kube-controller-manager(kubernetes.io/docs/reference/generated/kube-controller-manager/)中的其他控制器不同,ingress controller 是通过自定义实现运行的,而不是作为守护进程工作。在最新的 Kubernetes 版本 1.10 中,nginx ingress controller 是最稳定的,并且通常支持许多平台。有关部署的详细信息,请参见官方文档:github.com/kubernetes/ingress-nginx/blob/master/README.md。我们只会在 minikube 上演示我们的示例;请查看以下信息框以了解 ingress controller 的设置。
在 minikube 中启用 Ingress 功能:Ingress 在 minikube 中是一个附加功能。按照以下步骤在你的环境中启动此功能:
-
检查附加的 Ingress 是否启用:在终端执行命令
minikube addons list。如果未启用,显示为ingress: disabled,则应按照以下步骤继续操作。 -
启用 ingress:输入命令
minikube addons enable ingress,你将看到类似ingress was successfully enabled的输出。 -
再次检查附加组件列表,以验证上一步是否生效。我们期望 ingress 字段显示为
enabled。
下面是一个示例,演示如何与 Ingress 配合使用。我们将运行两个 Deployment 及其对应的 Service,并创建一个额外的 Ingress,将它们作为一个联合体暴露出来。一开始,我们将在 Kubernetes master 的主机文件中添加一个新主机名。这是一个简单的演示方法。如果你在生产环境中工作,一般的使用案例是将主机名作为记录添加到 DNS 服务器中:
// add a dummy hostname in local host file
$ sudo sh -c "echo `minikube ip` happy.k8s.io >> /etc/hosts"
我们的第一个 Kubernetes Deployment 和 Service 将是 echoserver,这是一个显示服务器和请求信息的虚拟 Service。对于另一对 Deployment 和 Service,我们将重用前一节中的 NodePort Service 示例:
$ cat echoserver.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: echoserver-deploy
spec:
replicas: 2
selector:
matchLabels:
app: echo
template:
metadata:
labels:
app: echo
spec:
containers:
- name: my-echo
image: gcr.io/google_containers/echoserver:1.8
---
apiVersion: v1
kind: Service
metadata:
name: echoserver-svc
spec:
selector:
app: echo
ports:
- protocol: TCP
port: 8080
targetPort: 8080
继续通过配置文件创建两组资源:
$ kubectl create -f echoserver.yaml
deployment.apps "echoserver-deploy" created
service "echoserver-svc" created
$ kubectl create -f nodeport-deployment.yaml
deployment.apps "nodeport-deploy" created
service "nodeport-svc" created
我们的第一个 Ingress 创建了两个在不同 URL /nginx 和 /echoserver 上监听的 Service,主机名为 happy.k8s.io,这是我们在本地主机文件中添加的虚拟主机。我们使用注解 rewrite-target 来确保流量重定向从根目录/开始。否则,客户端可能会因为路径错误而无法找到页面。我们可能会使用的更多注解列在github.com/kubernetes/ingress-nginx/blob/master/docs/user-guide/nginx-configuration/annotations.md:
$ cat ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: happy-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: spec:
rules:
- host: happy.k8s.io
http:
paths:
- path: /nginx
backend:
serviceName: nodeport-svc
servicePort: 8080
- path: /echoserver
backend:
serviceName: echoserver-svc
servicePort: 8080
然后,创建 Ingress 并立即检查其信息:
$ kubectl create -f ingress.yaml
ingress.extensions "happy-ingress" created
// "ing" is the abbreviation of "ingress"
$ kubectl describe ing happy-ingress
Name: happy-ingress
Namespace: default
Address:
Default backend: default-http-backend:80 (172.17.0.3:8080)
Rules:
Host Path Backends
---- ---- --------
happy.k8s.io
/nginx nodeport-svc:8080 (<none>)
/echoserver echoserver-svc:8080 (<none>)
Annotations:
nginx.ingress.kubernetes.io/rewrite-target
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal CREATE 14s ingress-controller Ingress default/happy-ingress
你可能会发现描述字段中没有 IP 地址。它将在第一次进行 DNS 查找后附加上来:
// verify the URL set in ingress rules
$ curl http://happy.k8s.io/nginx
...
<title>Welcome to nginx!</title>
...
$ curl http://happy.k8s.io/echoserver
Hostname: echoserver-deploy-5598f5796f-d8cr4
Pod Information:
-no pod information available-
Server values:
server_version=nginx: 1.13.3 - lua: 10008
...
// the IP address would be added after connection
$ kubectl get ing
NAME HOSTS ADDRESS PORTS AGE
happy-ingress happy.k8s.io 192.168.64.4 80 1m
尽管与 Ingress 一起使用并不像其他资源那样直接,因为你需要自己启动 Ingress 控制器实现,但它仍然使我们的应用得以暴露并具有灵活性。许多更稳定、用户友好的网络功能正在到来。跟上最新的更新,享受其中的乐趣吧!
还有更多...
在外部到内部通信的最后部分,我们了解了 Kubernetes Ingress——这个使服务协同工作并将请求调度到目标服务的资源。有没有什么类似的想法浮现在你脑海中?它听起来像是一个微服务架构,多个松耦合的服务组成的应用结构。一个复杂的应用会分布到多个较轻量的服务中。每个服务独立开发,同时都可以覆盖原有功能。像 Kubernetes 中的 Pods 等大量工作单元运行时是波动的,且可以由系统控制器在服务之间动态调度。然而,这种多层次的结构增加了网络的复杂性,并且可能带来额外的开销。
外部负载均衡器并不了解 Pods 的存在;它们只会将工作负载平衡到主机。如果某个主机上没有正在运行的服务 Pod,那么它会将负载重定向到其他主机。这种情况可能不符合用户对公平负载均衡的预期。而且,Pod 可能会意外崩溃,在这种情况下,进行故障转移并完成请求会变得非常困难。为弥补这些不足,专注于微服务网络管理的服务网格思想应运而生,旨在为像 Kubernetes 这样的编排系统提供更可靠和高效的通信:

简单的服务网格结构
上述图表展示了服务网格中的主要组件。它们协同工作,达成如下功能:
-
服务网格 Ingress:使用应用的 Ingress 规则来决定哪个服务应该处理传入的请求。它也可以是一个能够检查运行时策略的代理。
-
服务网格代理:每个节点上的代理不仅仅是引导数据包,还可以作为一个咨询代理,报告服务的整体状态。
-
服务网格服务发现池:为网格提供中央管理并推动对代理的控制。它的职责包括网络能力、身份验证、故障转移和负载均衡等过程。
尽管像 Linkerd (linkerd.io) 和 Istio (istio.io) 这样的知名服务网格实现还不成熟,尚未适合生产环境使用,但服务网格的理念不可忽视。
另见
Kubernetes 基于覆盖网络进行端口转发。在本章中,我们还使用 nginx 运行 Pods 和 Services。回顾前面的章节将帮助你更好地理解如何操作它。同时,看看以下的示例:
-
第一章中的创建覆盖网络和在 Kubernetes 中运行你的第一个容器教程,构建你自己的 Kubernetes 集群
-
第二章中的与 Pods 的工作和与 Services 的工作教程,深入了解 Kubernetes 概念
-
第五章中的将单体应用迁移到微服务教程,构建持续交付管道
确保容器的灵活使用
在 Kubernetes 中,Pod 指一组容器,它也是最小的计算单元。你可能已经在之前的教程中了解了 Pod 的基本用法。Pod 通常由 deployments 管理,并通过 services 暴露;在这种场景中,它们作为应用程序运行。
在本教程中,我们将讨论两个新特性:DaemonSets和StatefulSets。这两个特性可以管理具有更具体目的的 Pods。
准备工作
什么是Daemon-like Pod和Stateful Pod?Kubernetes 中的常规 Pod 将根据当前节点的资源使用情况和你的配置,确定并调度到特定的 Kubernetes 节点。
然而,Daemon-like Pod将在每个节点上创建。例如,如果你有三个节点,三个 Daemon-like Pods 将被创建并部署到每个节点上。每当添加新节点时,DaemonSets Pod 会自动部署到新节点。因此,它对于使用节点级别监控或日志收集非常有用。
另一方面,Stateful Pod会绑定到一些资源,如网络标识符(Pod 名称和 DNS)和持久卷(PV)。这也保证了在多个 Pod 的部署过程中以及滚动更新时的顺序。例如,如果你部署一个名为my-pod的 Pod,并将规模设置为4,那么 Pod 的名称将被分配为my-pod-0、my-pod-1、my-pod-2和my-pod-3。不仅 Pod 名称,DNS 和持久卷也会得到保留。例如,当my-pod-2由于资源短缺或应用崩溃被重新创建时,这些名称和卷将由一个新的 Pod 接管,并且这个 Pod 也会命名为my-pod-2。它对于一些基于集群的应用,如 HDFS 和 ElasticSearch,尤其有用。
在这个教程中,我们将演示如何使用 DaemonSets 和 StatefulSet;然而,为了更好地理解,建议使用多个 Kubernetes 节点的环境。为了实现这一点,minikube 并不理想,因此,建议使用 kubeadm/kubespray 来创建多节点环境。
使用 kubeadm 或 kubespray 设置 Kubernetes 集群的过程在第一章《构建你的 Kubernetes 集群》中有描述。
要确认是否有两个或更多节点,可以输入kubectl get nodes,如下所示,以检查你有多少个节点:
//this result indicates you have 2 nodes
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
node1 Ready master,node 6h v1.10.2
node2 Ready node 6h v1.10.2
此外,如果你想在本章稍后执行 StatefulSet 配方,你需要一个 StorageClass 来设置动态供应环境。它在 第二章 处理存储卷 部分中有描述,了解 Kubernetes 概念。建议使用公共云服务,如 AWS 和 GCP,并配置 CloudProvider;这将在 第六章 在 AWS 上构建 Kubernetes 和 第七章 在 GCP 上构建 Kubernetes 中进行详细描述。
要检查是否已配置 StorageClass,请使用 kubectl get sc:
//in Google Kubernetes Engine Environment
$ kubectl get sc
NAME PROVISIONER
standard (default) kubernetes.io/gce-pd
如何操作...
我们没有 CLI 用于创建 DaemonSets 或 StatefulSets。因此,我们将通过编写 YAML 文件中的所有配置来构建这两种资源类型。
Pod 作为 DaemonSets
如果创建了 Kubernetes DaemonSet,定义的 Pod 将会在每个节点上部署。确保每个节点上的运行容器占用相等的资源。在这种情况下,容器通常作为守护进程运行。
例如,以下模板包含一个 Ubuntu 镜像容器,该容器每半分钟检查一次其内存使用情况:
- 要将其构建为 DaemonSet,请执行以下代码块:
$ cat daemonset-free.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: ram-check
spec:
selector:
matchLabels:
name: checkRam
template:
metadata:
labels:
name: checkRam
spec:
containers:
- name: ubuntu-free
image: ubuntu
command: ["/bin/bash","-c","while true; do free; sleep 30; done"]
restartPolicy: Always
如同作业一样,选择器可以被忽略,但它会获取标签的值。我们将始终将 DaemonSet 的重启策略配置为 Always,确保每个节点都有一个 Pod 运行。
daemonset在kubectl命令中的缩写是ds,为了方便,在 CLI 中使用这个简短的命令:
$ kubectl create -f daemonset-free.yaml
daemonset.apps "ram-check" created
$ kubectl get ds
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
ram-check 2 2 2 2 2 <none> 5m
- 在这里,我们有两个 Pods 运行在分开的节点上。它们仍然可以在
pod渠道中被识别:
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
ram-check-6ldng 1/1 Running 0 9m 10.233.102.130 node1
ram-check-ddpdb 1/1 Running 0 9m 10.233.75.4 node2
- 使用子命令
kubectl logs评估结果是很好的:
$ kubectl logs ram-check-6ldng
total used free shared buff/cache available
Mem: 3623848 790144 329076 9128 2504628 2416976
Swap: 0 0 0
total used free shared buff/cache available
Mem: 3623848 786304 328028 9160 2509516 2420524
Swap: 0 0 0
total used free shared buff/cache available
Mem: 3623848 786344 323332 9160 2514172 2415944
Swap: 0 0 0
.
.
每当你将一个 Kubernetes 节点添加到现有集群时,DaemonSets 会自动识别并部署一个 Pod。
- 让我们再次检查当前 DaemonSets 的状态,以下是由于有两个节点而部署的两个 Pods:
$ kubectl get ds
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
ram-check 2 2 2 2 2 <none> 14m
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
node1 Ready master,node 6h v1.10.2
node2 Ready node 6h v1.10.2
- 所以,现在我们通过
kubespray或kubeadm添加一个新的节点到集群中,具体取决于你的设置:
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
node1 Ready master,node 6h v1.10.2
node2 Ready node 6h v1.10.2
node3 Ready node 3m v1.10.2
- 几分钟后,DaemonSet 的大小自动变为
3,与节点的数量一致:
$ kubectl get ds
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
ram-check 3 3 3 3 3 <none> 18m
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
ram-check-6ldng 1/1 Running 0 18m 10.233.102.130 node1
ram-check-ddpdb 1/1 Running 0 18m 10.233.75.4 node2
ram-check-dpdmt 1/1 Running 0 3m 10.233.71.0 node3
运行有状态的 Pod
让我们来看另一个使用场景。我们使用 Deployments/ReplicaSets 来复制 Pods。它扩展得很好且易于维护,Kubernetes 使用 Pod 的 IP 地址为 Pod 分配一个 DNS,例如 <Pod IP 地址>.<namespace>.pod.cluster.local。
以下示例展示了如何分配 Pod 的 DNS:
$ kubectl run apache2 --image=httpd --replicas=3
deployment "apache2" created
//one of Pod has an IP address as 10.52.1.8
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
apache2-55c684c66b-7m5zq 1/1 Running 0 5s 10.52.1.8 gke-chap7-default-pool-64212da9-z96q
apache2-55c684c66b-cjkcz 1/1 Running 0 1m 10.52.0.7 gke-chap7-default-pool-64212da9-8gzm
apache2-55c684c66b-v78tq 1/1 Running 0 1m 10.52.2.5 gke-chap7-default-pool-64212da9-bbs6
//another Pod can reach to 10-52-1-8.default.pod.cluster.local $ kubectl exec apache2-55c684c66b-cjkcz -- ping -c 2 10-52-1-8.default.pod.cluster.local
PING 10-52-1-8.default.pod.cluster.local (10.52.1.8): 56 data bytes
64 bytes from 10.52.1.8: icmp_seq=0 ttl=62 time=1.642 ms
64 bytes from 10.52.1.8: icmp_seq=1 ttl=62 time=0.322 ms
--- 10-52-1-8.default.pod.cluster.local ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max/stddev = 0.322/0.982/1.642/0.660 ms
然而,这个 DNS 条目并不保证会一直用于这个 Pod,因为 Pod 可能由于应用错误或节点资源不足而崩溃。在这种情况下,IP 地址可能会改变:
$ kubectl delete pod apache2-55c684c66b-7m5zq
pod "apache2-55c684c66b-7m5zq" deleted
//Pod IP address has been changed to 10.52.0.7
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
apache2-55c684c66b-7m5zq 0/1 Terminating 0 1m <none> gke-chap7-default-pool-64212da9-z96q
apache2-55c684c66b-cjkcz 1/1 Running 0 2m 10.52.0.7 gke-chap7-default-pool-64212da9-8gzm
apache2-55c684c66b-l9vqt 1/1 Running 0 7s 10.52.1.9 gke-chap7-default-pool-64212da9-z96q
apache2-55c684c66b-v78tq 1/1 Running 0 2m 10.52.2.5 gke-chap7-default-pool-64212da9-bbs6
//DNS entry also changed
$ kubectl exec apache2-55c684c66b-cjkcz -- ping -c 2 10-52-1-8.default.pod.cluster.local
PING 10-52-1-8.default.pod.cluster.local (10.52.1.8): 56 data bytes
92 bytes from gke-chap7-default-pool-64212da9-z96q.c.kubernetes-cookbook.internal (192.168.2.4): Destination Host Unreachable
92 bytes from gke-chap7-default-pool-64212da9-z96q.c.kubernetes-cookbook.internal (192.168.2.4): Destination Host Unreachable
--- 10-52-1-8.default.pod.cluster.local ping statistics ---
2 packets transmitted, 0 packets received, 100% packet loss
对于某些应用程序,这会导致问题;例如,如果您管理一个需要通过 DNS 或 IP 地址管理的集群应用程序。根据当前 Kubernetes 的实现,Pod 的 IP 地址不能被保留。那我们如何使用 Kubernetes 服务呢?服务可以保留 DNS 名称。不幸的是,创建与 Pod 相同数量的服务并不现实。在之前的情况下,创建三个服务,每个服务与三个 Pod 一一绑定。
Kubernetes 针对这种使用场景有一个解决方案,那就是使用 StatefulSet。它不仅保留 DNS,还保留持久卷,以确保绑定到相同的 Pod。即使 Pod 崩溃,StatefulSet 也能保证将相同的 DNS 和持久卷绑定到新 Pod。请注意,当前的 Kubernetes 实现并不会保留 IP 地址。
为了演示,使用 Hadoop 分布式文件系统 (HDFS) 启动一个 NameNode 和三个 DataNode。为此,使用来自 hub.docker.com/r/uhopper/hadoop/ 的 Docker 镜像,该镜像包含 NameNode 和 DataNode 镜像。此外,从 gist.github.com/polvi/34ef498a967de563dc4252a7bfb7d582 借用 YAML 配置文件 namenode.yaml 和 datanode.yaml,并稍作修改:
- 让我们为
namenode和datanode启动一个服务和 StatefulSet:
//create NameNode
$ kubectl create -f https://raw.githubusercontent.com/kubernetes-cookbook/second-edition/master/chapter3/3-4/namenode.yaml
service "hdfs-namenode-svc" created
statefulset "hdfs-namenode" created
$ kubectl get statefulset
NAME DESIRED CURRENT AGE
hdfs-namenode 1 1 19s
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
hdfs-namenode-0 1/1 Running 0 26s
//create DataNodes
$ kubectl create -f https://raw.githubusercontent.com/kubernetes-cookbook/second-edition/master/chapter3/3-4/datanode.yaml
statefulset "hdfs-datanode" created
$ kubectl get statefulset
NAME DESIRED CURRENT AGE
hdfs-datanode 3 3 50s
hdfs-namenode 1 1 5m
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
hdfs-datanode-0 1/1 Running 0 9m
hdfs-datanode-1 1/1 Running 0 9m
hdfs-datanode-2 1/1 Running 0 9m
hdfs-namenode-0 1/1 Running 0 9m
如您所见,Pod 的命名约定是 <StatefulSet-name>-<sequence number>。例如,NameNode Pod 的名称是 hdfs-namenode-0。DataNode Pod 的名称分别是 hdfs-datanode-0、hdfs-datanode-1 和 hdfs-datanode-2。
此外,NameNode 和 DataNode 都配置为无头模式的服务(通过 spec.clusterIP: None)。因此,您可以使用 DNS 通过 <pod-name>.<service-name>.<namespace>.svc.cluster.local 来访问这些 Pods。在这种情况下,NameNode 的 DNS 条目可能是 hdfs-namenode-0.hdfs-namenode-svc.default.svc.cluster.local*。
- 让我们检查 NameNode Pod 的 IP 地址,您可以使用
kubectl get pods -o wide来获取,如下所示:
//Pod hdfs-namenode-0 has an IP address as 10.52.2.8
$ kubectl get pods hdfs-namenode-0 -o wide
NAME READY STATUS RESTARTS AGE IP NODE
hdfs-namenode-0 1/1 Running 0 9m 10.52.2.8 gke-chapter3-default-pool-97d2e17c-0dr5
- 接下来,使用
kubectl exec登录(运行/bin/bash)到其中一个 DataNode,解析该 DNS 名称并检查 IP 地址是否为10.52.2.8:
$ kubectl exec hdfs-datanode-1 -it -- /bin/bash
root@hdfs-datanode-1:/#
root@hdfs-datanode-1:/# ping -c 1 hdfs-namenode-0.hdfs-namenode-svc.default.svc.cluster.local
PING hdfs-namenode-0.hdfs-namenode-svc.default.svc.cluster.local (10.52.2.8): 56 data bytes
...
...
一切看起来都好!为了演示,我们访问 HDFS Web 控制台查看 DataNode 的状态。
- 为此,使用
kubectl port-forward访问 NameNode 的 Web 端口(tcp/50070):
//check the status by HDFS web console
$ kubectl port-forward hdfs-namenode-0 :50070
Forwarding from 127.0.0.1:60107 -> 50070
- 上述结果表明,您本地机器的 TCP 端口
60107(您的结果可能不同)已经转发到 NameNode Pod 的 TCP 端口50070。因此,您可以通过网页浏览器访问http://127.0.0.1:60107/,如以下所示:

HDFS Web 控制台显示三个 DataNode
如您所见,三个 DataNode 已成功注册到 NameNode。DataNode 也使用了无头服务,因此相同的命名约定为 DataNode 分配了 DNS 名称。
它是如何工作的...
DaemonSets 和 StatefulSets:这两个概念相似,但行为不同,尤其是在 Pod 崩溃时。我们来看看它是如何工作的。
通过 DaemonSets 恢复 Pod
DaemonSets 会持续监控每个 Kubernetes 节点,因此当 Pod 崩溃时,DaemonSets 会在同一 Kubernetes 节点上重新创建它。
为了模拟这一过程,请返回到 DaemonSets 示例并使用kubectl delete pods手动删除node1上的现有 Pod,如下所示:
$ kubectl delete pod ram-check-6ldng
pod "ram-check-6ldng" deleted
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
ram-check-6ldng 1/1 Terminating 0 29m 10.233.102.132 node1
ram-check-ddpdb 1/1 Running 0 29m 10.233.75.5 node2
ram-check-dpdmt 1/1 Running 0 13m 10.233.71.0 node3
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
ram-check-ddpdb 1/1 Running 0 30m 10.233.75.5 node2
ram-check-dh5hq 1/1 Running 0 24s 10.233.102.135 node1 ram-check-dpdmt 1/1 Running 0 14m 10.233.71.0 node3
如您所见,新的 Pod 已自动创建,以恢复node1中的 Pod。请注意,Pod 名称已从ram-check-6ldng更改为ram-check-dh5hq——它已被分配了一个随机的后缀名称。在这个用例中,Pod 名称并不重要,因为我们没有使用主机名或 DNS 来管理这个应用程序。
通过 StatefulSet 恢复 Pod
StatefulSet 在 Pod 重建时与 DaemonSet 行为不同。在 StatefulSet 管理的 Pod 中,Pod 的名称始终是按顺序分配的,如hdfs-datanode-0、hdfs-datanode-1和hdfs-datanode-2,如果删除其中一个,新的 Pod 将继承相同的 Pod 名称。
为了模拟这一过程,让我们删除一个 DataNode(hdfs-datanode-1),看看 StatefulSet 如何重新创建 Pod:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
hdfs-datanode-0 1/1 Running 0 3m
hdfs-datanode-1 1/1 Running 0 2m
hdfs-datanode-2 1/1 Running 0 2m
hdfs-namenode-0 1/1 Running 0 23m
//delete DataNode-1
$ kubectl delete pod hdfs-datanode-1
pod "hdfs-datanode-1" deleted
//DataNode-1 is Terminating
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
hdfs-datanode-0 1/1 Running 0 3m
hdfs-datanode-1 1/1 Terminating 0 3m
hdfs-datanode-2 1/1 Running 0 2m
hdfs-namenode-0 1/1 Running 0 23m
//DataNode-1 is recreating automatically by statefulset
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
hdfs-datanode-0 1/1 Running 0 4m
hdfs-datanode-1 0/1 ContainerCreating 0 16s
hdfs-datanode-2 1/1 Running 0 3m
hdfs-namenode-0 1/1 Running 0 24m
//DataNode-1 is recovered
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
hdfs-datanode-0 1/1 Running 0 4m
hdfs-datanode-1 1/1 Running 0 22s
hdfs-datanode-2 1/1 Running 0 3m
hdfs-namenode-0 1/1 Running 0 24m
如您所见,相同的 Pod 名称(hdfs-datanode-1)已被分配。大约 10 分钟后(由于 HDFS 的心跳间隔),HDFS web 控制台显示旧 Pod 已标记为死亡,而新 Pod 处于服务中状态,如下所示:

一个 DataNode 挂掉时的状态
请注意,这对 HDFS 来说并不是一个完美的理想情况,因为 DataNode-1 丢失了数据,并期望从其他 DataNode 重新同步。如果数据量较大,重新同步可能需要很长时间才能完成。
幸运的是,StatefulSets 具有一个功能,可以在替换 Pod 时保留持久存储卷。我们来看看 HDFS DataNode 在 Pod 重建时如何保持数据。
还有更多内容...
带持久存储卷的 StatefulSet;它需要一个动态分配存储卷的StorageClass。由于每个 Pod 都是由 StatefulSet 创建的,因此它会创建一个具有不同标识符的持久卷声明(PVC)。如果您的 StatefulSets 指定了 PVC 的静态名称,多个 Pod 尝试附加同一个 PVC 时会出现问题。
如果您的集群中有StorageClass,请更新datanode.yaml,并添加spec.volumeClaimTemplates,如下所示:
$ curl https://raw.githubusercontent.com/kubernetes-cookbook/second-edition/master/chapter3/3-4/datanode-pv.yaml
...
volumeClaimTemplates:
- metadata:
name: hdfs-data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 10Gi
这告诉 Kubernetes 在 StatefulSet 创建新 Pod 时创建 PVC 和 PV。因此,Pod 模板(spec.template.spec.containers.volumeMounts)应指定hdfs-data,如下所示:
$ curl https://raw.githubusercontent.com/kubernetes-cookbook/second-edition/master/chapter3/3-4/datanode-pv.yaml
...
volumeMounts:
- mountPath: /hadoop/dfs/data
name: hdfs-data
让我们再次重建 HDFS 集群:
//delete DataNodes
$ kubectl delete -f https://raw.githubusercontent.com/kubernetes-cookbook/second-edition/master/chapter3/3-4/datanode.yaml
service "hdfs-datanode-svc" deleted
statefulset "hdfs-datanode" deleted
//delete NameNode
$ kubectl delete -f https://raw.githubusercontent.com/kubernetes-cookbook/second-edition/master/chapter3/3-4/namenode.yaml
service "hdfs-namenode-svc" deleted
statefulset "hdfs-namenode" deleted
//create NameNode again
$ kubectl create -f https://raw.githubusercontent.com/kubernetes-cookbook/second-edition/master/chapter3/3-4/namenode.yaml
service "hdfs-namenode-svc" created
statefulset "hdfs-namenode" created
//create DataNode which uses Persistent Volume (datanode-pv.yaml)
$ kubectl create -f https://raw.githubusercontent.com/kubernetes-cookbook/second-edition/master/chapter3/3-4/datanode-pv.yaml
service "hdfs-datanode-svc" created
statefulset "hdfs-datanode" created
//3 PVC has been created automatically
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
hdfs-data-hdfs-datanode-0 Bound pvc-bc79975d-f5bd-11e7-ac7a-42010a8a00ef 10Gi RWO standard 53s
hdfs-data-hdfs-datanode-1 Bound pvc-c753a336-f5bd-11e7-ac7a-42010a8a00ef 10Gi RWO standard 35s
hdfs-data-hdfs-datanode-2 Bound pvc-d1e10587-f5bd-11e7-ac7a-42010a8a00ef 10Gi RWO standard 17s
为了演示,使用kubectl exec访问 NameNode,然后将一些虚拟文件复制到 HDFS:
$ kubectl exec -it hdfs-namenode-0 -- /bin/bash
root@hdfs-namenode-0:/# hadoop fs -put /lib/x86_64-linux-gnu/* /
root@hdfs-namenode-0:/# exit
command terminated with exit code 255
//delete DataNode-1
$ kubectl delete pod hdfs-datanode-1
pod "hdfs-datanode-1" deleted
此时,DataNode-1正在重新启动,如下图所示。然而,DataNode-1的数据目录由 PVC 保持为hdfs-data-hdfs-datanode-1。新的 Pod hdfs-datanode-1将再次继承该 PVC:

StatefulSet 在重启时会保留 PVC/PV
因此,当 hdfs-datanode-1 恢复后,你访问 HDFS 时,不会看到任何数据丢失或重新同步过程:
$ kubectl exec -it hdfs-namenode-0 -- /bin/bash
root@hdfs-namenode-0:/# hdfs fsck /
Connecting to namenode via http://hdfs-namenode-0.hdfs-namenode-svc.default.svc.cluster.local:50070/fsck?ugi=root&path=%2F
FSCK started by root (auth:SIMPLE) from /10.52.1.13 for path / at Wed Jan 10 04:32:30 UTC 2018
....................................................................................................
.................................................................Status: HEALTHY
Total size: 22045160 B
Total dirs: 2
Total files: 165
Total symlinks: 0
Total blocks (validated): 165 (avg. block size 133607 B)
Minimally replicated blocks: 165 (100.0 %)
Over-replicated blocks: 0 (0.0 %)
Under-replicated blocks: 0 (0.0 %)
Mis-replicated blocks: 0 (0.0 %)
Default replication factor: 3
Average block replication: 3.0
Corrupt blocks: 0
Missing replicas: 0 (0.0 %)
Number of data-nodes: 3
Number of racks: 1
FSCK ended at Wed Jan 10 04:32:30 UTC 2018 in 85 milliseconds
The filesystem under path '/' is HEALTHY
如你所见,Pod 和 PV 配对由 StatefulSets 完全管理。如果你希望通过 kubectl scale 命令将 HDFS DataNode 扩展为双倍或数百倍——根据需求,这种方式非常方便:
//make double size of HDFS DataNodes
$ kubectl scale statefulset hdfs-datanode --replicas=6
statefulset "hdfs-datanode" scaled
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
hdfs-datanode-0 1/1 Running 0 20m
hdfs-datanode-1 1/1 Running 0 13m
hdfs-datanode-2 1/1 Running 0 20m
hdfs-datanode-3 1/1 Running 0 56s
hdfs-datanode-4 1/1 Running 0 38s
hdfs-datanode-5 1/1 Running 0 21s
hdfs-namenode-0 1/1 Running 0 21m
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
hdfs-data-hdfs-datanode-0 Bound pvc-bc79975d-f5bd-11e7-ac7a-42010a8a00ef 10Gi RWO standard 21m
hdfs-data-hdfs-datanode-1 Bound pvc-c753a336-f5bd-11e7-ac7a-42010a8a00ef 10Gi RWO standard 21m
hdfs-data-hdfs-datanode-2 Bound pvc-d1e10587-f5bd-11e7-ac7a-42010a8a00ef 10Gi RWO standard 21m
hdfs-data-hdfs-datanode-3 Bound pvc-888b6e0d-f5c0-11e7-ac7a-42010a8a00ef 10Gi RWO standard 1m
hdfs-data-hdfs-datanode-4 Bound pvc-932e6148-f5c0-11e7-ac7a-42010a8a00ef 10Gi RWO standard 1m
hdfs-data-hdfs-datanode-5 Bound pvc-9dd71bf5-f5c0-11e7-ac7a-42010a8a00ef 10Gi RWO standard 1m
你也可以使用 PV 来持久化 NameNode 的元数据。然而,由于 HDFS 架构,kubectl 扩展不太好用。为了实现 HDFS NameNode 的高可用性或横向扩展,请访问 HDFS 联邦文档:hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-hdfs/Federation.html。
另见
在本教程中,我们深入讨论了通过 DaemonSets 和 StatefulSet 进行 Kubernetes Pod 管理。它以特定方式管理 Pod,如每个节点一个 Pod 和一致的 Pod 名称。当 Deployments/ReplicaSets 的无状态 Pod 管理方式无法覆盖你的应用场景时,这种方式非常有用。有关更多信息,请参阅以下内容:
-
在第二章中 与 Pods 一起工作 的教程,深入了解 Kubernetes 概念
-
使用配置文件
在 Kubernetes 上提交作业
你的容器应用不仅设计用于守护进程(如 nginx),还设计用于一些任务完成后会退出的批处理作业。Kubernetes 支持这种场景;你可以将容器提交为一个作业,Kubernetes 会将其调度到合适的节点并执行作业。
本教程将讨论两个新特性:Jobs 和 CronJob。这两个特性可以让 Pod 有更多的用途,充分利用你的资源。
准备工作
从 Kubernetes 版本 1.2 开始,Kubernetes 作业作为一个稳定功能引入(apiVersion: batch/v1)。此外,从 Kubernetes 版本 1.10 起,CronJob 是一个 Beta 特性(apiVersion: batch/v1beta1)。
两者在 minikube 上都能良好运行,minikube 在第一章中有介绍,构建你自己的 Kubernetes 集群。因此,本教程将使用 minikube 版本 0.24.1。
如何操作...
提交作业到 Kubernetes 时,你可以定义三种类型的作业:
-
单一作业
-
重复作业
-
并行作业
Pod 作为单一作业
类似作业的 Pod 适用于测试你的容器,可以用于单元测试或集成测试;或者,也可以用于批处理程序:
- 在以下示例中,我们将编写一个作业模板,检查 Ubuntu 镜像中安装的包:
$ cat job-dpkg.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: package-check
spec:
template:
spec:
containers:
- name: package-check
image: ubuntu
command: ["dpkg-query", "-l"]
restartPolicy: Never
注意,Job 中创建的 Pod 的重启策略应该设置为 Never 或 OnFailure,因为一旦作业成功完成,Pod 就会终止。
- 现在,你已经准备好使用模板创建一个
job:
$ kubectl create -f job-dpkg.yaml
job.batch "package-check" created
- 创建
job对象后,可以验证 Pod 和 Job 的状态:
$ kubectl get jobs
NAME DESIRED SUCCESSFUL AGE
package-check 1 1 26s
- 该结果表示 Job 已经完成,执行(通过
SUCCESSFUL = 1)在26秒内完成。在这种情况下,Pod 已经消失:
$ kubectl get pods
No resources found, use --show-all to see completed objects.
- 如你所见,
kubectl命令提示我们可以使用--show-all或-a选项来查找已完成的 Pod,如下所示:
$ kubectl get pods --show-all
NAME READY STATUS RESTARTS AGE
package-check-hmjxj 0/1 Completed 0 3m
好的。那么为什么 Completed Pod 对象仍然存在?因为你可能想在程序结束后查看结果。你会发现一个 Pod 正在启动来处理此任务。这个 Pod 在过程结束时将很快停止。
- 使用子命令
kubectl logs获取结果:
$ kubectl logs package-check-hmjxj
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name Version Architecture Description
+++-========================-=============================-============-======================================================================
ii adduser 3.113+nmu3ubuntu4 all add and remove users and groups
ii apt 1.2.24 amd64 commandline package manager
ii base-files 9.4ubuntu4.5 amd64 Debian base system miscellaneous files
ii base-passwd 3.5.39 amd64 Debian base system master password and group files
ii bash 4.3-14ubuntu1.2 amd64 GNU Bourne Again SHell
.
.
.
- 请继续使用子命令
kubectl describe检查job package-check;Pod 完成和其他消息的确认将作为系统信息显示:
$ kubectl describe job package-check
Name: package-check
Namespace: default
Selector: controller-uid=9dfd1857-f5d1-11e7-8233-ae782244bd54
Labels: controller-uid=9dfd1857-f5d1-11e7-8233-ae782244bd54
job-name=package-check
Annotations: <none>
Parallelism: 1
Completions: 1
Start Time: Tue, 09 Jan 2018 22:43:50 -0800
Pods Statuses: 0 Running / 1 Succeeded / 0 Failed
.
.
.
- 然后,删除你刚刚创建的
job,用名称删除它。这也会删除已完成的 Pod:
$ kubectl delete jobs package-check
job.batch "package-check" deleted
$ kubectl get pods --show-all
No resources found.
创建一个可重复的 Job
用户还可以决定每个 Job 中应完成的任务数量。这对于解决一些随机问题和采样问题非常有用。让我们尝试在前面示例中的相同模板上:
- 添加
spec.completions项来指示 Pod 的数量:
$ cat job-dpkg-repeat.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: package-check
spec:
completions: 3
template:
spec:
containers:
- name: package-check
image: ubuntu
command: ["dpkg-query", "-l"]
restartPolicy: Never
- 创建该 Job 后,使用子命令
kubectl describe检查 Pod 的状态:
$ kubectl create -f job-dpkg-repeat.yaml
job.batch "package-check" created
$ kubectl describe jobs package-check
Name: package-check
Namespace: default
...
...
Annotations: <none>
Parallelism: 1
Completions: 3
Start Time: Tue, 09 Jan 2018 22:58:09 -0800
Pods Statuses: 0 Running / 3 Succeeded / 0 Failed
...
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 42s job-controller Created pod: package-check-f72wk
Normal SuccessfulCreate 32s job-controller Created pod: package-check-2mnw8
Normal SuccessfulCreate 27s job-controller Created pod: package-check-whbr6
如你所见,创建了三个 Pods 来完成这个 Job。如果你需要在特定时间重复运行程序,这会很有用。然而,正如你可能从前面的 Age 列中看到的,这些 Pods 是顺序运行的,一个接一个。这意味着第二个 Job 在第一个 Job 完成后才开始,第三个 Job 在第二个 Job 完成后才开始。
创建一个并行 Job
如果你的批处理 Job 之间没有状态或依赖关系,你可以考虑并行提交 Job。类似于 spec.completions 参数,Job 模板有一个 spec.parallelism 参数,用来指定你希望并行运行多少个 Job:
1. 重新使用可重复的 Job,但将其更改为指定 spec.parallelism: 3,如下所示:
$ cat job-dpkg-parallel.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: package-check
spec:
parallelism: 3
template:
spec:
containers:
- name: package-check
image: ubuntu
command: ["dpkg-query", "-l"]
restartPolicy: Never
- 结果类似于
spec.completions=3, 这使得3个 Pods 运行了你的应用程序:
$ kubectl get pods --show-all
NAME READY STATUS RESTARTS AGE
package-check-5jhr8 0/1 Completed 0 1m
package-check-5zlmx 0/1 Completed 0 1m
package-check-glkpc 0/1 Completed 0 1m
- 然而,如果通过
kubectl describe命令查看到Age列,说明有3个 Pods 是同时运行的:
$ kubectl describe jobs package-check
Name: package-check
Namespace: default
Selector: controller-uid=de41164e-f5d6-11e7-8233-ae782244bd54
Labels: controller-uid=de41164e-f5d6-11e7-8233-ae782244bd54
job-name=package-check
Annotations: <none>
Parallelism: 3
Completions: <unset>
…
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 24s job-controller Created pod: package-check-5jhr8
Normal SuccessfulCreate 24s job-controller Created pod: package-check-glkpc
Normal SuccessfulCreate 24s job-controller Created pod: package-check-5zlmx
在这种设置下,Kubernetes 可以将应用程序调度到可用的节点上运行,并且轻松地扩展你的 Job。如果你需要运行类似工作程序的应用程序,将一批 Pods 分配到不同的节点,这会非常有用。
使用 CronJob 调度运行 Job
如果你熟悉 UNIX CronJob 或 Java Quartz (www.quartz-scheduler.org),Kubernetes CronJob 是一个非常直观的工具,你可以定义特定的时间来重复运行你的 Kubernetes Job。
调度格式非常简单,它指定了以下五个项:
-
分钟(0 – 59)
-
小时(0 – 23)
-
每月的日期(1 – 31)
-
月份(1 – 12)
-
一周中的天数(0:星期日 - 6:星期六)
例如,如果您希望每年 11 月 12 日的早上 9:00 只运行一次 Job,向我发送生日祝福 😃,那么计划格式可能是 0 9 12 11 *。
您还可以使用斜杠 (/) 来指定步长值;例如,前一个 Job 的“每 5 分钟运行一次”间隔将采用以下计划格式:*/5 * * * *。
此外,还有一个可选参数 spec.concurrencyPolicy,您可以指定一个行为,如果前一个 Job 还没有完成,但下一个 Job 的计划时间即将到来,可以决定下一个 Job 如何运行。您可以设置以下选项:
-
允许:允许执行下一个 Job
-
禁止:跳过执行下一个 Job
-
替换:删除当前的 Job,然后执行下一个 Job
如果您设置为 允许,可能会存在在 Kubernetes 集群中积累一些未完成的 Jobs 的潜在风险。因此,在测试阶段,您应该设置为 禁止 或 替换 来监控 Job 的执行和完成:
$ cat cron-job.yaml
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: package-check
spec:
schedule: "*/5 * * * *"
concurrencyPolicy: "Forbid"
jobTemplate:
spec:
template:
spec:
containers:
- name: package-check
image: ubuntu
command: ["dpkg-query", "-l"]
restartPolicy: Never
//create CronJob
$ kubectl create -f cron-job.yaml
cronjob.batch "package-check" created
$ kubectl get cronjob
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
package-check */5 * * * * False 0 <none>
几秒钟后,Job 将根据您预定的时间被触发——在这种情况下是每 5 分钟一次。然后,您可以通过 kubectl get jobs 和 kubectl get pods -a 命令查看 Job 条目,如下所示:
//around 9 minutes later, 2 jobs have been submitted already
$ kubectl get jobs
NAME DESIRED SUCCESSFUL AGE
package-check-1515571800 1 1 7m
package-check-1515572100 1 1 2m
//correspond Pod are remain and find by -a option
$ kubectl get pods -a
NAME READY STATUS RESTARTS AGE
package-check-1515571800-jbzbr 0/1 Completed 0 7m
package-check-1515572100-bp5fz 0/1 Completed 0 2m
CronJob 会一直保留,直到您删除它;这意味着每 5 分钟,CronJob 会创建一个新的 Job 条目,并且相关的 Pods 也会不断创建。这会影响 Kubernetes 资源的消耗。因此,默认情况下,CronJob 会保留最多 3 个成功的 Jobs(由 spec.successfulJobsHistoryLimit 控制)和一个失败的 Job(由 spec.failedJobsHistoryLimit 控制)。您可以根据需要更改这些参数。
总体而言,CronJob 补充了 Jobs,使得 Jobs 可以在您应用程序中按预期的时间自动运行。您可以利用 CronJob 来运行一些报告生成任务、每日或每周批处理任务等。
它是如何工作的...
尽管 Jobs 和 CronJob 是 Pods 的特殊工具,但 Kubernetes 系统在它们和 Pods 之间有不同的管理机制。
对于 Job,它的选择器不能指向现有的 Pod。将一个由 deployment/ReplicaSets 控制的 Pod 作为 Job 使用是一个不好的主意。deployment/ReplicaSets 有一个期望的 Pod 数量在运行,这与 Job 的理想情况相违背:Pod 应该在完成任务后被删除。而 Deployment/ReplicaSets 中的 Pod 不会进入结束状态。
另请参阅
在这个教程中,我们执行了 Jobs 和 CronJob,展示了 Kubernetes Pod 的另一种用法,该 Pod 具有完成状态。即使 Pod 完成后,Kubernetes 也可以保留日志和 Pod 对象,以便您轻松检索结果。欲了解更多信息,请参见:
-
在 第二章中,与 Pods 一起工作,走进 Kubernetes 概念
-
与配置文件一起使用
与配置文件一起使用
Kubernetes 支持两种不同的文件格式,YAML 和 JSON。这两种格式可以描述 Kubernetes 的相同功能。
准备就绪
在学习如何编写 Kubernetes 配置文件之前,学习如何编写正确的模板格式非常重要。我们可以从 YAML 和 JSON 的官方网站了解它们的标准格式。
YAML
YAML 格式非常简单,语法规则少,因此即使是用户也很容易阅读和编写。要了解更多关于 YAML 的信息,可以参考以下网站链接:www.yaml.org/spec/1.2/spec.html。以下示例使用 YAML 格式来设置 nginx Pod:
$ cat nginx-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-nginx
labels:
env: dev
spec:
containers:
- name: my-nginx
image: nginx
ports:
- containerPort: 80
JSON
JSON 格式同样简单且易于用户阅读,但更适合程序使用。因为它有数据类型(数字、字符串、布尔值和对象),所以在系统之间交换数据时非常流行。从技术角度来说,YAML 是 JSON 的超集,因此 JSON 是有效的 YAML,但反过来不成立。要了解更多关于 JSON 的信息,可以参考以下网站链接:json.org/。
以下这个 Pod 示例与之前的 YAML 格式相同,只不过使用了 JSON 格式:
$ cat nginx-pod.json
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "my-nginx",
"labels": {
"env": "dev"
}
},
"spec": {
"containers": [
{
"name": "my-nginx",
"image": "nginx",
"ports": [
{
"containerPort": 80
}
]
}
]
}
}
如何操作...
Kubernetes 有一个通过验证配置格式定义的模式;在第一次使用配置文件运行子命令 create 后,可以生成该模式。缓存的模式将存储在 .kube/cache/discovery/<SERVICE_IP>_<PORT> 下,具体位置取决于你运行的 API 服务器版本:
// create the resource by either YAML or JSON file introduced before
$ kubectl create -f nginx-pod.yaml
// or
$ kubectl create -f nginx-pod.json
// as an example of v1.10.0, the content of schema directory may look like following
// you would have different endpoint of server
ll ~/.kube/cache/discovery/192.168.99.100_8443/
total 76
drwxr-xr-x 18 nosus nosus 4096 May 6 10:10 ./
drwxr-xr-x 4 nosus nosus 4096 May 6 10:00 ../
drwxr-xr-x 3 nosus nosus 4096 May 6 10:00 admissionregistration.k8s.io/
drwxr-xr-x 3 nosus nosus 4096 May 6 10:00 apiextensions.k8s.io/
drwxr-xr-x 4 nosus nosus 4096 May 6 10:00 apiregistration.k8s.io/
drwxr-xr-x 5 nosus nosus 4096 May 6 10:00 apps/
drwxr-xr-x 4 nosus nosus 4096 May 6 10:00 authentication.k8s.io/
drwxr-xr-x 4 nosus nosus 4096 May 6 10:00 authorization.k8s.io/
drwxr-xr-x 4 nosus nosus 4096 May 6 10:00 autoscaling/
drwxr-xr-x 4 nosus nosus 4096 May 6 10:00 batch/
drwxr-xr-x 3 nosus nosus 4096 May 6 10:00 certificates.k8s.io/
drwxr-xr-x 3 nosus nosus 4096 May 6 10:00 events.k8s.io/
drwxr-xr-x 3 nosus nosus 4096 May 6 10:00 extensions/
drwxr-xr-x 3 nosus nosus 4096 May 6 10:00 networking.k8s.io/
drwxr-xr-x 3 nosus nosus 4096 May 6 10:00 policy/
drwxr-xr-x 4 nosus nosus 4096 May 6 10:00 rbac.authorization.k8s.io/
-rwxr-xr-x 1 nosus nosus 3898 May 6 10:10 servergroups.json*
drwxr-xr-x 4 nosus nosus 4096 May 6 10:00 storage.k8s.io/
drwxr-xr-x 2 nosus nosus 4096 May 6 10:10 v1/
每个列出的目录代表一个 API 类别。在每个目录的最底层,你会看到一个名为 serverresources.json 的文件,它明确定义了该 API 类别所涵盖的每个资源。然而,也有一些替代且更简便的方式来查看模式。在 Kubernetes 的官方网站上,我们可以获取有关如何编写特定资源配置文件的详细信息。请继续查看最新版本的官方 API 文档:kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/。该网页包含三个面板:从左到右分别是资源列表、描述、以及 HTTP 请求的输入和输出,或者是 kubectl 命令。以 Deployment 为例,你可以点击资源列表中的 Deployment v1 app,最左边的面板就会显示以下截图:

Kubernetes 部署 API 文档
但是,我们怎么知道前面图示中标记的容器部分的设置细节呢?在对象描述的字段部分,有两个值。第一个值,像apiVersion,表示名称,第二个值,像string,表示类型。类型可以是整数、字符串、数组或其他资源对象。因此,为了搜索 deployment 的容器配置,我们需要了解对象层次结构的结构。首先,根据网页上的示例配置文件,容器的对象层次是spec.template.spec.containers.。因此,从点击 Deployment 字段下的超链接spec DeploymentSpec开始,这个字段是资源对象的类型,然后逐层搜索。最终,你可以在此页面找到详细信息:kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#container-v1-core。
用于追踪 Deployment 配置中容器的解决方案:以下是前面示例的解决方案:
-
点击 spec DeploymentSpec
-
点击模板 PodTemplateSpec
-
点击 spec PodSpec
-
点击容器 容器数组
现在你明白了!
仔细查看容器配置的定义。以下是一些常见的描述,你应该注意:
-
类型:用户应始终为每个项目设置相应的类型。
-
可选与否:某些项目标记为可选,意味着它不是必需的,可以作为默认值应用,如果没有指定,则可以不设置。
-
不能更新:如果某项标记为无法更新,则在资源创建时已固定。你需要重新创建一个新的,而不是更新它。
-
只读:一些项目标记为
只读,例如 UID。这些项由 Kubernetes 生成。如果你在配置文件中指定了这些项,它们将被忽略。
另一种检查架构的方法是通过 Swagger UI。Kubernetes 使用 Swagger(https://swagger.io/)和 OpenAPI(www.openapis.org)生成 REST API。然而,默认情况下,Swagger 的 Web 控制台在 API 服务器中是禁用的。要启用你自己的 Kubernetes API 服务器的 Swagger UI,只需在启动 API 服务器时添加--enable-swagger-ui=true标志。然后,通过访问https://<KUBERNETES_MASTER>:<API_SERVER_PORT>/swagger-ui,你可以成功地通过 Web 控制台浏览 API 文档:

Kubernetes API 的 Swagger Web 控制台
它是如何工作的...
让我们介绍一些在配置文件中创建 Pod、Deployment 和 Service 所必需的项目。
Pod
| 项目 | 类型 | 示例 |
|---|---|---|
apiVersion |
字符串 | v1 |
kind |
字符串 | Pod |
metadata.name |
字符串 | my-nginx-pod |
spec |
v1.PodSpec |
|
v1.PodSpec.containers |
数组[v1.Container] |
|
v1.Container.name |
字符串 | my-nginx |
v1.Container.image |
字符串 | nginx |
部署
| 项 | 类型 | 示例 |
|---|---|---|
apiVersion |
字符串 | apps/v1beta1 |
kind |
字符串 | Deployment |
metadata.name |
字符串 | my-nginx-deploy |
spec |
v1.DeploymentSpec |
|
v1.DeploymentSpec.template |
v1.PodTemplateSpec |
|
v1.PodTemplateSpec.metadata.labels |
字符串映射 | env: test |
v1.PodTemplateSpec.spec |
v1.PodSpec |
my-nginx |
v1.PodSpec.containers |
数组[v1.Container] |
与 Pod 相同 |
服务
| 项 | 类型 | 示例 |
|---|---|---|
apiVersion |
字符串 | v1 |
kind |
字符串 | Service |
metadata.name |
字符串 | my-nginx-svc |
spec |
v1.ServiceSpec |
|
v1.ServiceSpec.selector |
字符串映射 | env: test |
v1.ServiceSpec.ports |
数组[v1.ServicePort] |
|
v1.ServicePort.protocol |
字符串 | TCP |
v1.ServicePort.port |
整数 | 80 |
请检查代码包文件minimal-conf-resource.yaml,以找到这些具有最小配置的三个资源。
另见
本配方描述了如何查找和理解配置语法。Kubernetes 有一些详细的选项来定义容器和组件。更多细节将在以下配方中描述如何定义 Pod、Deployment 和 Service:
- 在第二章的与 Pods 一起工作、Deployment API和与 Services 一起工作配方中,走进 Kubernetes 概念
第四章:构建高可用性集群
在本章中,我们将覆盖以下内容:
-
etcd 集群化
-
构建多个主节点
简介
避免单点故障是我们始终需要牢记的一个概念。在本章中,你将学习如何在 Kubernetes 中构建高可用性组件。我们还将介绍如何构建一个三节点的 etcd 集群,并使用多节点的主控节点。
etcd 集群化
etcd 存储着 Kubernetes 中的网络信息和状态。任何数据丢失都可能至关重要。在生产环境中,强烈建议对 etcd 进行集群化。etcd 支持集群功能;一个包含 N 个成员的集群可以容忍最多 (N-1)/2 个节点的故障。通常,有三种机制可以用来创建 etcd 集群,具体如下:
-
静态
-
etcd 发现
-
DNS 发现
静态方式是一种简单的启动 etcd 集群的方法,前提是我们在启动之前已经配置好了所有 etcd 成员。然而,通常情况下,我们会使用一个现有的 etcd 集群来引导新成员的加入。这时,发现方法就派上用场了。发现服务利用现有集群来启动自己,它允许新的 etcd 集群成员找到其他已存在的成员。在本章节中,我们将讨论如何通过静态方式和 etcd 发现手动引导 etcd 集群。
我们在第一章,构建你自己的 Kubernetes 集群中,已经学习了如何使用 kubeadm 和 kubespray。当本文撰写时,kubeadm 中的高可用性功能仍在开发中。官方文档中建议定期备份你的 etcd 节点。另一方面,我们介绍的另一个工具 kubespray 原生支持多节点的 etcd。在本章中,我们还将描述如何在 kubespray 中配置 etcd。
准备工作
在学习更灵活的 etcd 集群搭建方式之前,我们需要了解 etcd 目前有两个主要版本,分别是 v2 和 v3。etcd3 是一个较新的版本,旨在提供更稳定、高效和可靠的性能。以下是一个简单的比较,介绍它们在实现上的主要差异:
| etcd2 | etcd3 | |
|---|---|---|
| 协议 | http | gRPC |
| 键过期 | TTL 机制 | 租约 |
| 监视器 | HTTP 长轮询 | 通过双向 gRPC 流 |
etcd3 旨在成为 etcd2 的下一代版本。etcd3 默认支持 gRPC 协议。gRPC 使用 HTTP2,允许通过一个 TCP 连接进行多个 RPC 流。而在 etcd2 中,每个 HTTP 请求必须在每次请求时重新建立连接。为了处理键的过期问题,在 etcd2 中,TTL 会附加到一个键上;客户端应定期刷新键以检查是否有键过期。这将导致大量的连接建立。
在 etcd3 中,引入了租约(lease)概念。一个租约可以附加多个键;当租约到期时,它会删除所有附加的键。对于观察者,etcd2 客户端使用 HTTP 进行长轮询——这意味着每个观察都要开启一个 TCP 连接。而 etcd3 使用双向 gRPC 流实现,允许多个流共享同一个连接。
虽然推荐使用 etcd3,但是一些部署仍然使用 etcd2。我们仍然会介绍如何使用这些工具来实现集群,因为 etcd 中的数据迁移文档齐全且过程平稳。如需更多信息,请参考升级迁移步骤:coreos.com/blog/migrating-applications-etcd-v3.html。
在我们开始构建 etcd 集群之前,必须决定需要多少个成员。etcd 集群的大小实际上取决于你要创建的环境。在生产环境中,至少推荐使用三个成员。这样,集群至少能容忍一次永久性故障。在本例中,我们将使用三个成员作为开发环境的示例:
| 名称/主机名 | IP 地址 |
|---|---|
ip-172-31-3-80 |
172.31.3.80 |
ip-172-31-14-133 |
172.31.14.133 |
ip-172-31-13-239 |
172.31.13.239 |
其次,etcd 服务需要 2379 端口(对于旧版使用 4001 端口)进行 etcd 客户端通信,以及 2380 端口 进行对等节点通信。这些端口必须在你的环境中暴露。
如何操作...
有很多方法可以配置 etcd 集群。通常,你会使用 kubespray、kops(在 AWS 上)或其他配置工具。
在这里,我们将简单地展示如何进行手动安装。其实也很简单:
// etcd installation script
$ cat install-etcd.sh
ETCD_VER=v3.3.0
# ${DOWNLOAD_URL} could be ${GOOGLE_URL} or ${GITHUB_URL}
GOOGLE_URL=https://storage.googleapis.com/etcd
GITHUB_URL=https://github.com/coreos/etcd/releases/download
DOWNLOAD_URL=${GOOGLE_URL}
# delete tmp files
rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
rm -rf /tmp/etcd && rm -rf /etc/etcd && mkdir -p /etc/etcd
curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /etc/etcd --strip-components=1
rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
# check etcd version
/etc/etcd/etcd --version
这个脚本会将 etcd 二进制文件放到 /etc/etcd 文件夹中。你可以将它们放到不同的位置。在这种情况下,我们需要 sudo 权限才能将文件放到 /etc 下:
// install etcd on linux
# sudo sh install-etcd.sh
…
etcd Version: 3.3.0
Git SHA: c23606781
Go Version: go1.9.3
Go OS/Arch: linux/amd64
我们现在使用的版本是 3.3.0。检查 etcd 二进制文件在你的机器上是否能正常工作后,我们可以将其添加到默认的 $PATH 中。这样,每次执行 etcd 命令时就不需要包含 /etc/etcd 路径:
$ export PATH=/etc/etcd:$PATH
$ export ETCDCTL_API=3
你也可以将其放入 .bashrc 或 .bash_profile 中,让它默认生效。
在我们至少配置了三个 etcd 服务器后,就可以让它们配对在一起了。
静态机制
静态机制是建立集群的最简单方式。然而,每个成员的 IP 地址应该预先知道。这意味着如果你在云提供商环境中启动 etcd 集群,静态机制可能不太实用。因此,etcd 还提供了一个发现机制,可以从现有集群启动集群。
为了确保 etcd 通信的安全,etcd 支持 TLS 通道来加密对等节点之间的通信,以及客户端和服务器之间的通信。每个成员都需要拥有一个独特的密钥对。在本节中,我们将展示如何使用自动生成的证书来构建集群。
在 CoreOs GitHub 上,有一个方便的工具可以帮助我们生成自签名证书(github.com/coreos/etcd/tree/v3.2.15/hack/tls-setup)。克隆仓库后,我们需要修改 config/req-csr.json 下的配置文件。以下是一个示例:
// sample config, put under $repo/config/req-csr.json
$ cat config/req-csr.json
{
"CN": "etcd",
"hosts": [
"172.31.3.80",
"172.31.14.133",
"172.31.13.239"
],
"key": {
"algo": "ecdsa",
"size": 384
},
"names": [
{
"O": "autogenerated",
"OU": "etcd cluster",
"L": "the internet"
}
]
}
在下一步中,我们需要安装 Go(golang.org/)并设置 $GOPATH:
$ export GOPATH=$HOME/go
$ make
然后证书将在 ./certs/ 目录下生成。
首先,我们需要设置一个引导配置,声明哪些成员将包含在集群中:
// set as environment variables, or alternatively, passing by –-initial-cluster and –-initial-cluster-state parameters inside launch command.
# ETCD_INITIAL_CLUSTER="etcd0=http://172.31.3.80:2380,etcd1=http://172.31.14.133:2380,etcd2=http://172.31.13.239:2380"
ETCD_INITIAL_CLUSTER_STATE=new
在所有三个节点中,我们需要分别启动 etcd 服务器:
// first node: 172.31.3.80
# etcd --name etcd0 --initial-advertise-peer-urls https://172.31.3.80:2380 \
--listen-peer-urls https://172.31.3.80:2380 \
--listen-client-urls https://172.31.3.80:2379,https://127.0.0.1:2379 \
--advertise-client-urls https://172.31.3.80:2379 \
--initial-cluster-token etcd-cluster-1 \
--initial-cluster etcd0=https://172.31.3.80:2380,etcd1=https://172.31.14.133:2380,etcd2=https://172.31.13.239:2380 \
--initial-cluster-state new \
--auto-tls \
--peer-auto-tls
然后,你将看到以下输出:
2018-02-06 22:15:20.508687 I | etcdmain: etcd Version: 3.3.0
2018-02-06 22:15:20.508726 I | etcdmain: Git SHA: c23606781
2018-02-06 22:15:20.508794 I | etcdmain: Go Version: go1.9.3
2018-02-06 22:15:20.508824 I | etcdmain: Go OS/Arch: linux/amd64
…
2018-02-06 22:15:21.439067 N | etcdserver/membership: set the initial cluster version to 3.0
2018-02-06 22:15:21.439134 I | etcdserver/api: enabled capabilities for version 3.0
让我们启动第二个 etcd 服务:
// second node: 172.31.14.133
$ etcd --name etcd1 --initial-advertise-peer-urls https://172.31.14.133:2380 \
--listen-peer-urls https://172.31.14.133:2380 \
--listen-client-urls https://172.31.14.133:2379,https://127.0.0.1:2379 \
--advertise-client-urls https://172.31.14.133:2379 \
--initial-cluster-token etcd-cluster-1 \
--initial-cluster etcd0=https://172.31.3.80:2380,etcd1=https://172.31.14.133:2380,etcd2=https://172.31.13.239:2380 \
--initial-cluster-state new \
--auto-tls \
--peer-auto-tls
你将在控制台中看到类似的日志:
2018-02-06 22:15:20.646320 I | etcdserver: starting member ce7c9e3024722f01 in cluster a7e82f7083dba2c1
2018-02-06 22:15:20.646384 I | raft: ce7c9e3024722f01 became follower at term 0
2018-02-06 22:15:20.646397 I | raft: newRaft ce7c9e3024722f01 [peers: [], term: 0, commit: 0, applied: 0, lastindex: 0, lastterm: 0]
2018-02-06 22:15:20.646403 I | raft: ce7c9e3024722f01 became follower at term 1
…
2018-02-06 22:15:20.675928 I | rafthttp: starting peer 25654e0e7ea045f8...
2018-02-06 22:15:20.676024 I | rafthttp: started HTTP pipelining with peer 25654e0e7ea045f8
2018-02-06 22:15:20.678515 I | rafthttp: started streaming with peer 25654e0e7ea045f8 (writer)
2018-02-06 22:15:20.678717 I | rafthttp: started streaming with peer 25654e0e7ea045f8 (writer)
它开始与我们之前的节点(25654e0e7ea045f8)配对。让我们在第三个节点中触发以下命令:
// third node: 172.31.13.239
$ etcd --name etcd2 --initial-advertise-peer-urls https://172.31.13.239:2380 \
--listen-peer-urls https://172.31.13.239:2380 \
--listen-client-urls https://172.31.13.239:2379,https://127.0.0.1:2379 \
--advertise-client-urls https://172.31.13.239:2379 \
--initial-cluster-token etcd-cluster-1 \
--initial-cluster etcd0=https://172.31.3.80:2380,etcd1=https://172.31.14.133:2380,etcd2=https://172.31.13.239:2380 \
--initial-cluster-state new \
--auto-tls \
--peer-auto-tls
// in node2 console, it listens and receives new member (4834416c2c1e751e) added.
2018-02-06 22:15:20.679548 I | rafthttp: starting peer 4834416c2c1e751e...
2018-02-06 22:15:20.679642 I | rafthttp: started HTTP pipelining with peer 4834416c2c1e751e
2018-02-06 22:15:20.679923 I | rafthttp: started streaming with peer 25654e0e7ea045f8 (stream Message reader)
2018-02-06 22:15:20.680190 I | rafthttp: started streaming with peer 25654e0e7ea045f8 (stream MsgApp v2 reader)
2018-02-06 22:15:20.680364 I | rafthttp: started streaming with peer 4834416c2c1e751e (writer)
2018-02-06 22:15:20.681880 I | rafthttp: started peer 4834416c2c1e751e
2018-02-06 22:15:20.681909 I | rafthttp: added peer 4834416c2c1e751e
After all nodes are in, it'll start to elect the leader inside the cluster, we could find it in the logs:
2018-02-06 22:15:21.334985 I | raft: raft.node: ce7c9e3024722f01 elected leader 4834416c2c1e751e at term 27
...
2018-02-06 22:17:21.510271 N | etcdserver/membership: updated the cluster version from 3.0 to 3.3
2018-02-06 22:17:21.510343 I | etcdserver/api: enabled capabilities for version 3.3
集群已设置完毕。我们应该检查它是否正常运行:
$ etcdctl cluster-health
member 25654e0e7ea045f8is healthy: got healthy result from http://172.31.3.80:2379
member ce7c9e3024722f01 is healthy: got healthy result from http://172.31.14.133:2379
member 4834416c2c1e751e is healthy: got healthy result from http://172.31.13.239:2379
发现机制
发现机制提供了一种更灵活的方式来创建集群。它不需要提前知道其他对等节点的 IP。它利用现有的 etcd 集群来引导新的集群。在这一部分,我们将演示如何利用这一机制启动一个三节点的 etcd 集群:
- 首先,我们需要一个现有的三节点配置的集群。幸运的是,
etcd官方网站提供了一个发现服务(https://discovery.etcd.io/new?size=n);n 将是你etcd集群中的节点数,已经可以使用:
// get a request URL
# curl -w "n" 'https://discovery.etcd.io/new?size=3'
https://discovery.etcd.io/f6a3fb54b3fd1bb02e26a89fd40df0e8
- 然后我们可以使用该 URL 来轻松引导集群。命令行与静态机制几乎相同。我们需要做的是将
–initial-cluster改为–discovery,这个参数用于指定发现服务的 URL:
// in node1, 127.0.0.1 is used for internal client listeneretcd -name ip-172-31-3-80 -initial-advertise-peer-urls http://172.31.3.80:2380 -listen-peer-urls http://172.31.3.80:2380 -listen-client-urls http://172.31.3.80:2379,http://127.0.0.1:2379 -advertise-client-urls http://172.31.3.80:2379 -discovery https://discovery.etcd.io/f6a3fb54b3fd1bb02e26a89fd40df0e8
// in node2, 127.0.0.1 is used for internal client listener
etcd -name ip-172-31-14-133 -initial-advertise-peer-urls http://172.31.14.133:2380 -listen-peer-urls http://172.31.14.133:2380 -listen-client-urls http://172.31.14.133:2379,http://127.0.0.1:2379 -advertise-client-urls http://172.31.14.133:2379 -discovery https://discovery.etcd.io/f6a3fb54b3fd1bb02e26a89fd40df0e8
// in node3, 127.0.0.1 is used for internal client listener
etcd -name ip-172-31-13-239 -initial-advertise-peer-urls http://172.31.13.239:2380 -listen-peer-urls http://172.31.13.239:2380 -listen-client-urls http://172.31.13.239:2379,http://127.0.0.1:2379 -advertise-client-urls http://172.31.13.239:2379 -discovery https://discovery.etcd.io/f6a3fb54b3fd1bb02e26a89fd40df0e8
- 让我们仔细查看 node1 的日志:
2018-02-10 04:58:03.819963 I | etcdmain: etcd Version: 3.3.0
...
2018-02-10 04:58:03.820400 I | embed: listening for peers on http://172.31.3.80:2380
2018-02-10 04:58:03.820427 I | embed: listening for client requests on
127.0.0.1:2379
2018-02-10 04:58:03.820444 I | embed: listening for client requests on 172.31.3.80:2379
2018-02-10 04:58:03.947753 N | discovery: found self f60c98e749d41d1b in the cluster
2018-02-10 04:58:03.947771 N | discovery: found 1 peer(s), waiting for 2 more
2018-02-10 04:58:22.289571 N | discovery: found peer 6645fe871c820573 in the cluster
2018-02-10 04:58:22.289628 N | discovery: found 2 peer(s), waiting for 1 more
2018-02-10 04:58:36.907165 N | discovery: found peer 1ce61c15bdbb20b2 in the cluster
2018-02-10 04:58:36.907192 N | discovery: found 3 needed peer(s)
...
2018-02-10 04:58:36.931319 I | etcdserver/membership: added member 1ce61c15bdbb20b2 [http://172.31.13.239:2380] to cluster 29c0e2579c2f9563
2018-02-10 04:58:36.931422 I | etcdserver/membership: added member 6645fe871c820573 [http://172.31.14.133:2380] to cluster 29c0e2579c2f9563
2018-02-10 04:58:36.931494 I | etcdserver/membership: added member f60c98e749d41d1b [http://172.31.3.80:2380] to cluster 29c0e2579c2f9563
2018-02-10 04:58:37.116189 I | raft: f60c98e749d41d1b became leader at term 2
我们可以看到第一个节点等待其他两个成员加入,并将成员添加到集群中,在第二轮选举中成为了领导者:
- 如果你查看其他服务器的日志,可能会发现一些线索,表明某些成员投票给了当前的领导者:
// in node 2
2018-02-10 04:58:37.118601 I | raft: raft.node: 6645fe871c820573 elected leader f60c98e749d41d1b at term 2
- 我们还可以使用成员列表检查当前的领导者:
# etcdctl member list
1ce61c15bdbb20b2: name=ip-172-31-13-239 peerURLs=http://172.31.13.239:2380 clientURLs=http://172.31.13.239:2379 isLeader=false
6645fe871c820573: name=ip-172-31-14-133 peerURLs=http://172.31.14.133:2380 clientURLs=http://172.31.14.133:2379 isLeader=false
f60c98e749d41d1b: name=ip-172-31-3-80 peerURLs=http://172.31.3.80:2380 clientURLs=http://172.31.3.80:2379 isLeader=true
- 然后我们可以确认当前领导者是
172.31.3.80。我们还可以使用etcdctl检查集群健康:
# etcdctl cluster-health
member 1ce61c15bdbb20b2 is healthy: got healthy result from http://172.31.13.239:2379
member 6645fe871c820573 is healthy: got healthy result from http://172.31.14.133:2379
member f60c98e749d41d1b is healthy: got healthy result from http://172.31.3.80:2379
cluster is healthy
- 如果我们通过
etcdctl命令移除当前领导者:
# etcdctl member remove f60c98e749d41d1b
- 我们可能会发现当前领导者已经发生变化:
# etcdctl member list
1ce61c15bdbb20b2: name=ip-172-31-13-239 peerURLs=http://172.31.13.239:2380 clientURLs=http://172.31.13.239:2379 isLeader=false
6645fe871c820573: name=ip-172-31-14-133 peerURLs=http://172.31.14.133:2380 clientURLs=http://172.31.14.133:2379 isLeader=true
通过使用 etcd 发现机制,我们可以轻松地设置集群,etcd 还提供了许多 API 供我们使用。我们可以利用它来检查集群的统计信息:
- 例如,使用
/stats/leader来检查当前集群视图:
# curl http://127.0.0.1:2379/v2/stats/leader
{"leader":"6645fe871c820573","followers":{"1ce61c15bdbb20b2":{"latency":{"current":0.002463,"average":0.0038775,"standardDeviation":0.0014144999999999997,"minimum":0.002463,"maximum":0.005292},"counts":{"fail":0,"success":2}}}}
有关 API 的更多信息,请查看官方 API 文档:coreos.com/etcd/docs/latest/v2/api.html。
在 EC2 中构建集群
CoreOS 在 AWS 中构建了 CloudFormation,以帮助您动态地引导 AWS 上的集群。我们需要做的就是启动一个 CloudFormation 模板并设置参数,然后就可以开始了。模板中的资源包含自动扩展设置和网络入口(安全组)。请注意,这些 etcd 是运行在 CoreOS 上的。要登录服务器,首先您需要在 KeyPair 参数中设置您的密钥对名称,然后使用命令 ssh –i $your_keypair core@$ip 登录到服务器。
kubeadm
如果您正在使用 kubeadm (github.com/kubernetes/kubeadm) 来引导您的 Kubernetes 集群,很遗憾,在写作时,HA 支持仍在进行中(v.1.10)。该集群被创建为单一主节点,并配置了单个 etcd。您需要定期备份 etcd 以确保数据安全。有关更多信息,请参阅 Kubernetes 官方网站上的 kubeadm 限制说明 (kubernetes.io/docs/setup/independent/create-cluster-kubeadm/#limitations)。
kubespray
另一方面,如果您使用 kubespray 来配置服务器,kubespray 本地支持多节点 etcd。您需要做的就是在配置文件(inventory.cfg)中的 etcd 部分添加多个节点:
# cat inventory/inventory.cfg
my-master-1 ansible_ssh_host=<master_ip>
my-node-1 ansible_ssh_host=<node_ip>
my-etcd-1 ansible_ssh_host=<etcd1_ip>
my-etcd-2 ansible_ssh_host=<etcd2_ip>
my-etcd-3 ansible_ssh_host=<etcd3_ip>
[kube-master]
my-master-1
[etcd]
my-etcd-1
my-etcd-2
my-etcd-3
[kube-node]
my-master-1
my-node-1
然后您就可以配置一个包含三节点 etcd 的集群:
// provision a cluster
$ ansible-playbook -b -i inventory/inventory.cfg cluster.yml
在启动 ansible playbook 后,它将配置角色、创建用户、检查是否已经在第一个主节点生成所有证书,并生成和分发证书。在部署的最后,ansible 会检查每个组件的健康状态。
Kops
Kops 是在 AWS 上创建 Kubernetes 集群的最有效方式。通过 kops 配置文件,您可以轻松地在云端启动一个自定义集群。要构建一个 etcd 多节点集群,您可以在 kops 配置文件中使用以下部分:
etcdClusters:
- etcdMembers:
- instanceGroup: my-master-us-east-1a
name: my-etcd-1
- instanceGroup: my-master-us-east-1b
name: my-etcd-2
- instanceGroup: my-master-us-east-1c
name: my-etcd-3
通常,instanceGroup 意味着自动扩展组。您还需要在配置文件中声明相关的 intanceGroup my-master-us-east-1x。我们将在 第六章,在 AWS 上构建 Kubernetes 中深入了解它。默认情况下,在本书撰写时,kops 仍使用 etcd2;您可以在 kops 配置文件中添加一个版本键,例如 version: 3.3.0,并在每个 instanceGroup 下设置。
另请参见
-
在 第一章,构建您的 Kubernetes 集群 中,使用 kubespray 在 Linux 上设置 Kubernetes 集群
-
本章的 构建多个主节点 部分
-
第六章,在 AWS 上构建 Kubernetes
-
在 第九章,日志与监控 中的 处理 etcd 日志
构建多个主节点
主节点在 Kubernetes 系统中作为核心组件。其职责包括以下几点:
-
从 etcd 服务器推送和拉取信息
-
充当请求的门户
-
将任务分配给节点
-
监控正在运行的任务
三个主要的守护进程使主节点能够履行上述职责;下图显示了前述要点的活动:

Kubernetes 主节点与其他组件之间的交互
如您所见,主节点是工作节点和客户端之间的通信者。因此,如果主节点崩溃,将会是一个问题。一个多主节点的 Kubernetes 系统不仅具有容错能力,而且还具备负载均衡的功能。如果其中一个主节点崩溃,也不会有问题,因为其他主节点仍然可以处理任务。我们称这种基础设施设计为高可用性,简称 HA。为了支持 HA 架构,不再只有一个 API 服务器用于访问数据存储和处理请求。多个 API 服务器分布在不同的主节点上,可以同时处理任务并缩短响应时间。
准备工作
关于构建多主节点系统,您需要了解以下几个简要概念:
-
在主节点前添加一个负载均衡器服务器。负载均衡器将成为节点和客户端访问的新端点。
-
每个主节点运行自己的 API 服务器。
-
系统中只能有一个调度器和一个控制器管理器,以避免不同守护进程在管理容器时发生冲突。为了实现这一设置,我们在调度器和控制器管理器中启用
--leader-elect标志。只有获得租约的那个守护进程才能承担任务。
在本方法中,我们将通过kubeadm构建一个双主节点系统,这一方法在扩展更多主节点时类似。用户也可以使用其他工具来构建 HA Kubernetes 集群。我们的目标是阐明一般概念。
在开始之前,除了主节点外,您还需要准备系统中其他必要的组件:
-
两台 Linux 主机,稍后将作为主节点设置。这些机器应该配置为 kubeadm 主节点。请参考第一章中的使用 kubeadm 设置 Kubernetes 集群,构建您自己的 Kubernetes 集群。您应在两台主机上完成软件包安装和系统配置先决条件部分。
-
为主节点配置负载均衡器。如果您在公共云上操作,情况会更简单,例如 AWS 的 ELB和 GCE 的负载均衡。
-
一个 etcd 集群。请查看本章中的集群 etcd配置方法。
如何实现…
我们将使用配置文件运行 kubeadm,以实现自定义的守护进程执行。请按照接下来的章节将多个主节点作为一个组来操作。
设置第一个主节点
首先,我们将设置一个主节点,准备好 HA 环境。就像最初的步骤一样,使用 kubeadm 启动集群时,开始时启用并启动 kubelet 是很重要的。这样,它就可以在kube-system命名空间中作为 Pod 运行的守护进程:
// you are now in the terminal of host for first master
$ sudo systemctl enable kubelet && sudo systemctl start kubelet
接下来,使用自定义的 kubeadm 配置文件启动主节点服务:
$ cat custom-init-1st.conf
apiVersion: kubeadm.k8s.io/v1alpha1
kind: MasterConfiguration
api:
advertiseAddress: "<FIRST_MASTER_IP>"
etcd:
endpoints:
- "<ETCD_CLUSTER_ENDPOINT>"
apiServerCertSANs:
- "<FIRST_MASTER_IP>"
- "<SECOND_MASTER_IP>"
- "<LOAD_BALANCER_IP>"
- "127.0.0.1"
token: "<CUSTOM_TOKEN: [a-z0-9]{6}.[a-z0-9]{16}>"
tokenTTL: "0"
apiServerExtraArgs:
endpoint-reconciler-type: "lease"
这个配置文件中有多个需要根据你的环境设置的值。IP 地址的设置比较简单。请注意,现在你正在设置第一个主节点;<FIRST_MASTER_IP>变量将是你当前主机的物理 IP 地址。<ETCD_CLUSTER_ENDPOINT>将是类似"http://<IP>:<PORT>"的格式,表示 etcd 集群的负载均衡器。<CUSTOM_TOKEN>应该符合指定格式(例如,123456.aaaabbbbccccdddd)。在分配好所有符合你系统的变量后,现在可以执行它:
$ sudo kubeadm init --config=custom-init-1st.conf
你可能会遇到“Swap 不支持”的错误信息。可以在kubeadm init命令后添加额外的--ignore-preflight-errors=Swap标志,避免这个中断。
确保在两个主节点的文件中都进行了更新。
我们需要通过以下命令完成客户端功能:
$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config
就像使用 kubeadm 启动单节点集群时一样,缺少容器网络接口时,附加组件kube-dns将始终处于挂起状态。我们将使用 CNI Calico 作为演示。你也可以应用其他适合 kubeadm 的 CNI:
$ kubectl apply -f https://docs.projectcalico.org/v2.6/getting-started/kubernetes/installation/hosted/kubeadm/1.6/calico.yaml
现在你可以添加更多的主节点了。
使用现有证书设置其他主节点
和上次的会话类似,首先启动并启用kubelet:
// now you're in the second master
$ sudo systemctl enable kubelet && sudo systemctl start kubelet
在设置好第一个主节点之后,我们应将新生成的证书和密钥共享到整个系统中。这确保了主节点在同样的安全方式下被保护:
$ sudo scp -r root@$FIRST_MASTER_IP:/etc/kubernetes/pki/* /etc/kubernetes/pki/
你会发现一些文件,如证书或密钥,已被直接复制到/etc/kubernetes/pki/,这些文件只能由 root 用户访问。然而,我们将删除apiserver.crt和apiserver.key文件,因为这些文件应该根据第二个主节点的主机名和 IP 地址生成,但共享的客户端证书ca.crt也参与了生成过程:
$ sudo rm /etc/kubernetes/pki/apiserver.*
接下来,在执行主节点初始化命令之前,请在配置文件中更改第二个主节点的 API 广播地址。它应该是第二个主节点的 IP 地址,也就是当前主机的 IP 地址。第二个主节点的配置文件与第一个主节点的配置文件非常相似。
区别在于我们需要指明etcd服务器的信息,并避免创建新的etcd服务器:
// Please modify the change by your case
$ cat custom-init-2nd.conf
apiVersion: kubeadm.k8s.io/v1alpha1
kind: MasterConfiguration
api:
advertiseAddress: "<SECOND_MASTER_IP>"
...
继续执行kubeadm init命令,记录init命令最后一行显示的kubeadm join命令,以便稍后将节点添加进来,并启用客户端 API 权限:
$ sudo kubeadm init --config custom-init-2nd.conf
// copy the "kubeadm join" command showing in the output
$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config
然后,检查当前的节点;你会发现有两个主节点:
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
master01 Ready master 8m v1.10.2
master02 Ready master 1m v1.10.2
在 HA 集群中添加节点
一旦主节点准备好,你可以将节点添加到系统中。这个节点应该已经完成了作为 kubeadm 集群中的工作节点的先决配置。并且在开始时,你应该像主节点一样启动 kubelet:
// now you're in the second master
$ sudo systemctl enable kubelet && sudo systemctl start kubelet
之后,你可以继续推送你复制的加入命令。然而,请将主节点的 IP 地址更改为负载均衡器的 IP:
// your join command should look similar to following one
$ sudo kubeadm join --token <CUSTOM_TOKEN> <LOAD_BALANCER_IP>:6443 --discovery-token-ca-cert-hash sha256:<HEX_STRING>
然后,你可以跳到第一个主节点或第二个主节点来检查节点的状态:
// you can see the node is added
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
master01 Ready master 4h v1.10.2
master02 Ready master 3h v1.10.2
node01 Ready <none> 22s v1.10.2
它是如何工作的……
为了验证我们的高可用集群,查看 kube-system 命名空间中的 pod:
$ kubectl get pod -n kube-system
NAME READY STATUS RESTARTS AGE
calico-etcd-6bnrk 1/1 Running 0 1d
calico-etcd-p7lpv 1/1 Running 0 1d
calico-kube-controllers-d554689d5-qjht2 1/1 Running 0 1d
calico-node-2r2zs 2/2 Running 0 1d
calico-node-97fjk 2/2 Running 0 1d
calico-node-t55l8 2/2 Running 0 1d
kube-apiserver-master01 1/1 Running 0 1d
kube-apiserver-master02 1/1 Running 0 1d
kube-controller-manager-master01 1/1 Running 0 1d
kube-controller-manager-master02 1/1 Running 0 1d
kube-dns-6f4fd4bdf-xbfvp 3/3 Running 0 1d
kube-proxy-8jk69 1/1 Running 0 1d
kube-proxy-qbt7q 1/1 Running 0 1d
kube-proxy-rkxwp 1/1 Running 0 1d
kube-scheduler-master01 1/1 Running 0 1d
kube-scheduler-master02 1/1 Running 0 1d
这些 pod 作为系统守护进程在工作:Kubernetes 系统服务,如 API 服务器,Kubernetes 附加组件,如 DNS 服务器,以及 CNI 组件;这里我们使用的是 Calico。但等等!当你仔细查看这些 pod 时,你可能会好奇,为什么控制器管理器和调度器会在两个主节点上运行?难道高可用集群中不应该只有一个主节点吗?
正如我们在前一节中理解的那样,我们应该避免在 Kubernetes 系统中运行多个控制器管理器和多个调度器。因为它们可能会同时尝试接管请求,这不仅会产生冲突,还会浪费计算资源。实际上,在通过 kubeadm 启动整个系统时,控制器管理器和调度器默认启用了 leader-elect 标志:
// check flag leader-elect on master node
$ sudo cat /etc/kubernetes/manifests/kube-controller-manager.yaml
apiVersion: v1
kind: Pod
metadata:
annotations:
scheduler.alpha.kubernetes.io/critical-pod: ""
creationTimestamp: null
labels:
component: kube-controller-manager
tier: control-plane
name: kube-controller-manager
namespace: kube-system
spec:
containers:
- command:
- kube-controller-manager
...
- --leader-elect=true...
你可能会发现调度器也设置了 leader-elect。然而,为什么还是有多个 pod?事实是,具有相同角色的 pod 中有一个是空闲的。我们可以通过查看系统端点来获取详细信息:
// ep is the abbreviation of resource type "endpoints"
$ kubectl get ep -n kube-system
NAME ENDPOINTS AGE
calico-etcd 192.168.122.201:6666,192.168.122.202:6666 1d
kube-controller-manager <none> 1d
kube-dns 192.168.241.67:53,192.168.241.67:53 1d
kube-scheduler <none> 1d
// check endpoint of controller-manager with YAML output format
$ kubectl get ep kube-controller-manager -n kube-system -o yaml
apiVersion: v1
kind: Endpoints
metadata:
annotations:
control-plane.alpha.kubernetes.io/leader: '{"holderIdentity":"master01_bf4e22f7-4f56-11e8-aee3-52540048ed9b","leaseDurationSeconds":15,"acquireTime":"2018-05-04T04:51:11Z","renewTime":"2018-05-04T05:28:34Z","leaderTransitions":0}'
creationTimestamp: 2018-05-04T04:51:11Z
name: kube-controller-manager
namespace: kube-system
resourceVersion: "3717"
selfLink: /api/v1/namespaces/kube-system/endpoints/kube-controller-manager
uid: 5e2717b0-0609-11e8-b36f-52540048ed9b
以 kube-controller-manager 的端点为例:它没有附加到 pod 或服务的虚拟 IP(与 kube-scheduler 相同)。如果我们进一步深入查看这个端点,会发现 kube-controller-manager 的端点依赖于 annotations 来记录租约信息;它还依赖于 resourceVersion 来进行 pod 映射和传递流量。根据 kube-controller-manager 端点的注释信息,是我们的第一个主节点控制了这个权限。让我们检查两个主节点上的控制器管理器:
// your pod should be named as kube-controller-manager-<HOSTNAME OF MASTER>
$ kubectl logs kube-controller-manager-master01 -n kube-system | grep "leader"
I0504 04:51:03.015151 1 leaderelection.go:175] attempting to acquire leader lease kube-system/kube-controller-manager...
...
I0504 04:51:11.627737 1 event.go:218] Event(v1.ObjectReference{Kind:"Endpoints", Namespace:"kube-system", Name:"kube-controller-manager", UID:"5e2717b0-0609-11e8-b36f-52540048ed9b", APIVersion:"v1", ResourceVersion:"187", FieldPath:""}): type: 'Normal' reason: 'LeaderElection' master01_bf4e22f7-4f56-11e8-aee3-52540048ed9b became leader
如你所见,只有一个主节点作为领导者并处理请求,而另一个主节点则保持空闲,获取租约但不执行任何操作。
为了进行进一步的测试,我们正在尝试移除当前的主控 pod,以观察会发生什么。在通过 kubectl 请求删除系统 pod 的部署时,kubeadm Kubernetes 会创建一个新的 pod,因为它确保会启动任何位于/etc/kubernetes/manifests 目录下的应用程序。因此,为了避免 kubeadm 的自动恢复,我们将配置文件从清单目录中移除。这样可以使停机时间足够长,以便放弃主控权限:
// jump into the master node of leader
// temporary move the configuration file out of kubeadm's control
$ sudo mv /etc/kubernetes/manifests/kube-controller-manager.yaml ./
// check the endpoint
$ kubectl get ep kube-controller-manager -n kube-system -o yaml
apiVersion: v1
kind: Endpoints
metadata:
annotations:
control-plane.alpha.kubernetes.io/leader: '{"holderIdentity":"master02_4faf95c7-4f5b-11e8-bda3-525400b06612","leaseDurationSeconds":15,"acquireTime":"2018-05-04T05:37:03Z","renewTime":"2018-05-04T05:37:47Z","leaderTransitions":1}'
creationTimestamp: 2018-05-04T04:51:11Z
name: kube-controller-manager
namespace: kube-system
resourceVersion: "4485"
selfLink: /api/v1/namespaces/kube-system/endpoints/kube-controller-manager
uid: 5e2717b0-0609-11e8-b36f-52540048ed9b
subsets: null
/etc/kubernetes/manifests 目录在 kubelet 中由 --pod-manifest-path flag 定义。检查 /etc/systemd/system/kubelet.service.d/10-kubeadm.conf, 这是 kubelet 的系统守护进程配置文件,更多详细信息请参见 kubelet 的帮助信息。
现在轮到另一个节点唤醒它的控制器管理器并使其开始工作。一旦你把控制器管理器的配置文件放回去,你会发现旧的领导者现在正在等待租约:
$ kubectl logs kube-controller-manager-master01 -n kube-system
I0504 05:40:10.218946 1 controllermanager.go:116] Version: v1.10.2
W0504 05:40:10.219688 1 authentication.go:55] Authentication is disabled
I0504 05:40:10.219702 1 insecure_serving.go:44] Serving insecurely on 127.0.0.1:10252
I0504 05:40:10.219965 1 leaderelection.go:175] attempting to acquire leader lease kube-system/kube-controller-manager...
另见
在阅读本食谱之前,你应该已经掌握了通过 kubeadm 安装单主节点的基本概念。参考此处提到的相关食谱,了解如何自动构建一个多主节点系统:
-
在 第一章 中,通过 kubeadm 设置 Kubernetes 集群在 Linux 上,构建你自己的 Kubernetes 集群
-
集群化 etcd
第五章:构建持续交付管道
在本章中,我们将涵盖以下内容:
-
从单体架构迁移到微服务
-
与私有 Docker 注册表的配合
-
与 Jenkins 集成
介绍
Kubernetes 非常适合具有微服务架构的应用程序。然而,大多数旧应用程序是以单体式架构构建的。我们将给你一个关于如何从单体架构过渡到微服务世界的思路。至于微服务,如果你手动进行部署,它将变得很繁琐。我们将学习如何通过协调 Jenkins、Docker 注册表和 Kubernetes 来构建自己的持续交付管道。
从单体架构迁移到微服务
通常,应用程序架构是单体设计,包含 模型-视图-控制器(MVC)并将所有组件打包成一个大型二进制文件。单体设计有一些好处,例如组件之间的延迟较低,所有功能都在一个简单的包中,且容易部署和测试。
然而,单体设计有一些缺点,因为二进制文件会变得越来越大。每次添加或修改代码时,你总是需要注意副作用,因此发布周期会变得更长。
使用容器和 Kubernetes 在你的应用程序中使用微服务时提供了更多的灵活性。微服务架构非常简单,可以将其分为一些模块或一些服务类,并结合 MVC 进行组织:

单体与微服务设计
每个微服务通过 RESTful 或一些标准网络 API 提供 远程过程调用(RPC)给其他微服务。其好处在于每个微服务是独立的,添加或修改代码时副作用最小。可以独立发布周期,因此它与敏捷软件开发方法论完美契合,并允许这些微服务的重用来构建另一个应用程序,从而建立微服务生态系统。
准备就绪
准备简单的微服务程序。为了推送和拉取你的微服务,请提前注册 Docker hub (hub.docker.com/) 并创建一个免费的 Docker ID。
如果你将 Docker 镜像推送到 Docker hub,它将是公开的;任何人都可以拉取你的镜像。因此,请不要将任何机密信息放入镜像中。
一旦你成功登录到 Docker ID,你将被重定向到你的 Dashboard 页面,如下所示:

登录 Docker hub 后
如何操作...
准备好微服务和前端 WebUI 作为 Docker 镜像。然后,使用 Kubernetes 复制控制器和服务进行部署。
微服务
通过以下步骤构建一个提供简单数学功能的微服务:
- 这是一个使用 Python
Flask的简单微服务 (flask.pocoo.org/):
$ cat entry.py
from flask import Flask, request
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
@app.route("/power/<int:base>/<int:index>")
def power(base, index):
return "%d" % (base ** index)
@app.route("/addition/<int:x>/<int:y>")
def add(x, y):
return "%d" % (x+y)
@app.route("/substraction/<int:x>/<int:y>")
def substract(x, y):
return "%d" % (x-y)
if __name__ == "__main__":
app.run(host='0.0.0.0')
- 准备一个如下所示的
Dockerfile来构建 Docker 镜像:
$ cat Dockerfile
FROM ubuntu:14.04
# Update packages
RUN apt-get update -y
# Install Python Setuptools
RUN apt-get install -y python-setuptools git telnet curl
# Install pip
RUN easy_install pip
# Bundle app source
ADD . /src
WORKDIR /src
# Add and install Python modules
RUN pip install Flask
# Expose
EXPOSE 5000
# Run
CMD ["python", "entry.py"]
- 然后,使用
docker build命令构建 Docker 镜像,如下所示:
//name as “your_docker_hub_id/my-calc”
$ sudo docker build -t hidetosaito/my-calc .
Sending build context to Docker daemon 3.072 kB
Step 1 : FROM ubuntu:14.04
---> 6cc0fc2a5ee3
Step 2 : RUN apt-get update -y
---> Using cache
(snip)
Step 8 : EXPOSE 5000
---> Running in 7c52f4bfe373
---> 28f79bb7481f
Removing intermediate container 7c52f4bfe373
Step 9 : CMD python entry.py
---> Running in 86b39c727572
---> 20ae465bf036
Removing intermediate container 86b39c727572
Successfully built 20ae465bf036
//verity your image
$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
hidetosaito/my-calc latest 20ae465bf036 19 seconds ago 284 MB
ubuntu 14.04 6cc0fc2a5ee3 3 weeks ago 187.9 MB
- 然后,使用
docker login命令登录到 Docker hub:
//type your username, password and e-mail address in Docker hub
$ sudo docker login
Username: hidetosaito
Password:
Email: hideto.saito@yahoo.com
WARNING: login credentials saved in /home/ec2-user/.docker/config.json
Login Succeeded
- 最后,使用
docker push命令将镜像注册到您的 Docker hub 仓库,如下所示:
//push to your docker index
$ sudo docker push hidetosaito/my-calc
The push refers to a repository [docker.io/hidetosaito/my-calc] (len: 1)
20ae465bf036: Pushed
(snip)
92ec6d044cb3: Pushed
latest: digest: sha256:203b81c5a238e228c154e0b53a58e60e6eb3d1563293483ce58f48351031a474 size: 19151
- 访问 Docker hub 后,您可以在仓库中看到您的微服务:

您的微服务 Docker 镜像在 Docker hub 上
前端 WebUI
按照以下步骤构建使用前述微服务的 WebUI:
- 这是一个简单的前端 WebUI,它也使用 Python
Flask:
$ cat entry.py
import os
import httplib
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route("/")
def index():
return render_template('index.html')
@app.route("/add", methods=['POST'])
def add():
#
# from POST parameters
#
x = int(request.form['x'])
y = int(request.form['y'])
#
# from Kubernetes Service(environment variables)
#
my_calc_host = os.environ['MY_CALC_SERVICE_SERVICE_HOST']
my_calc_port = os.environ['MY_CALC_SERVICE_SERVICE_PORT']
#
# REST call to MicroService(my-calc)
#
client = httplib.HTTPConnection(my_calc_host, my_calc_port)
client.request("GET", "/addition/%d/%d" % (x, y))
response = client.getresponse()
result = response.read()
return render_template('index.html', add_x=x, add_y=y,
add_result=result)
if __name__ == "__main__":
app.debug = True
app.run(host='0.0.0.0')
Kubernetes 服务会生成 Kubernetes 服务名称和端口号作为环境变量,传递给其他 pods。因此,环境变量的名称和 Kubernetes 服务名称必须一致。在这种情况下,my-calc 服务名称必须是 my-calc-service。
- 前端 WebUI 使用
FlaskHTML 模板;它类似于 PHP 和 JSP,entry.py会将参数传递给模板(index.html)以渲染 HTML:
$ cat templates/index.html
<html>
<body>
<div>
<form method="post" action="/add">
<input type="text" name="x" size="2"/>
<input type="text" name="y" size="2"/>
<input type="submit" value="addition"/>
</form>
{% if add_result %}
<p>Answer : {{ add_x }} + {{ add_y }} = {{ add_result }}</p>
{% endif %}
</div>
</body>
</html>
Dockerfile与微服务my-calc完全相同。因此,最终,文件结构将如下所示。请注意,index.html是一个 jinja2 模板文件,因此将其放在/templates目录下:
/Dockerfile
/entry.py
/templates/index.html
- 然后,构建 Docker 镜像并推送到 Docker hub,如下所示:
为了将您的镜像推送到 Docker hub,您需要使用 Docker 登录命令进行登录。只需要登录一次;系统会检查 ~/.docker/config.json 来读取凭证。
//build frontend Webui image
$ sudo docker build -t hidetosaito/my-frontend .
//login to docker hub
$ sudo docker login
//push frontend webui image
$ sudo docker push hidetosaito/my-frontend
- 访问 Docker hub 后,您可以在仓库中看到您的 WebUI 应用程序:

微服务和前端 WebUI 镜像在 Docker Hub 上
它是如何工作的……
让我们准备两个 YAML 配置文件,使用 Kubernetes 启动微服务容器和前端 WebUI 容器。
微服务
微服务(my-calc)使用 Kubernetes 部署和服务,但它只需要与其他 pod 进行通信。换句话说,不需要将其暴露到外部 Kubernetes 网络中。因此,服务类型设置为 ClusterIP:
$ cat my-calc.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-calc-deploy
spec:
replicas: 2
selector:
matchLabels:
run: my-calc
template:
metadata:
labels:
run: my-calc
spec:
containers:
- name: my-calc
image: hidetosaito/my-calc
---
apiVersion: v1
kind: Service
metadata:
name: my-calc-service
spec:
ports:
- protocol: TCP
port: 5000
type: ClusterIP
selector:
run: my-calc
使用 kubectl 命令加载 my-calc pod,如下所示:
$ kubectl create -f my-calc.yaml
deployment.apps "my-calc-deploy" created
service "my-calc-service" created
前端 WebUI
前端 WebUI 也使用部署和服务,但它暴露端口(TCP 端口 30080),以便从外部网页浏览器访问:
$ cat my-frontend.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-frontend-deploy
spec:
replicas: 2
selector:
matchLabels:
run: my-frontend
template:
metadata:
labels:
run: my-frontend
spec:
containers:
- name: my-frontend
image: hidetosaito/my-frontend
---
apiVersion: v1
kind: Service
metadata:
name: my-frontend-service
spec:
ports:
- protocol: TCP
port: 5000
nodePort: 30080
type: NodePort
selector:
run: my-frontend
$ kubectl create -f my-frontend.yaml
deployment.apps "my-frontend-deploy" created
service "my-frontend-service" created
让我们尝试使用网页浏览器访问 my-frontend-service。您可以访问任何 Kubernetes 节点的 IP 地址;指定端口号 30080。如果您使用的是 minikube,只需输入 minikube service my-frontend-service 进行访问。然后,您可以看到 my-frontend 应用程序,如下所示:

访问前端 WebUI
当您点击加号按钮时,它将参数传递给微服务(my-calc)。微服务计算加法(没错,就是加法!),然后将结果返回到前端 WebUI,如下所示:

从微服务中获取结果并渲染 HTML
所以现在,可以独立地扩展前端 WebUI 和微服务的 Pod。例如,将 WebUI Pod 从 2 扩展到 8,将微服务 Pod 从 2 扩展到 16,如图所示:
$ kubectl get deploy
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
my-calc-deploy 2 2 2 2 30m
my-frontend-deploy 2 2 2 2 28m
$ kubectl scale deploy my-frontend-deploy --replicas=8
deployment "my-frontend-deploy" scaled
$ kubectl scale deploy my-calc-deploy --replicas=16
deployment "my-calc-deploy" scaled
$ kubectl get deploy
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
my-calc-deploy 16 16 16 16 31m
my-frontend-deploy 8 8 8 8 29m
此外,如果需要修复一些 bug,例如,如果前端需要进行验证
输入参数以检查它是数字还是字符串(是的,如果您输入字符串并
然后提交,它会显示错误!),它不会影响构建和部署周期。
微服务:

前端和微服务 Pod 和服务
此外,如果您想添加另一个微服务,例如减法微服务,您可能需要创建另一个 Docker 镜像,并使用另一个部署和服务进行部署,这样它将与当前的微服务独立。然后,您可以不断积累自己的微服务生态系统,以便在其他应用程序中重用。
使用私有 Docker 注册表
一旦您开始通过 Docker 构建微服务应用程序,您将需要一个 Docker 注册表来存放您的容器镜像。Docker hub 为您提供免费的公共仓库,但在某些情况下,由于业务需求或组织政策,您可能希望将您的镜像设为私有。
Docker hub 提供私有仓库,仅允许经过身份验证的用户推送和拉取您的镜像,并且对其他用户不可见。然而,免费计划只有一个配额(仓库)。您可以付费增加私有仓库的数量,但如果您采用微服务架构,您将需要大量的私有仓库:

Docker hub 私有仓库价格表
使用付费计划的 Docker hub 是设置私有注册表的最简单方式,但也有一些其他方法可以设置自己的私有 Docker 注册表,且不受限制的 Docker 镜像配额位于您的网络内。此外,您还可以使用其他云提供的注册表服务来管理您的私有注册表。
准备工作
在本食谱中,我们将向您展示设置自己私有注册表的三种不同方式:
-
使用 Kubernetes 运行私有注册表镜像 (
hub.docker.com/_/registry/) -
使用 Amazon 弹性容器注册表 (
aws.amazon.com/ecr/) -
使用 Google 容器注册表 (
cloud.google.com/container-registry/)
使用 Kubernetes 设置私有注册表时,您可以使用您自己的 Kubernetes 集群,无论是在私有云还是公共云中,这样您可以完全控制并利用大部分物理资源。
另一方面,当使用公共云提供的服务,如 AWS 或 GCP 时,你可以省心地管理服务器和存储。无论你需要什么,这些公共云都为你提供弹性资源。我们只需要将凭据设置到 Kubernetes 并让节点知晓。接下来的几种方案将介绍这三种不同的选择。
使用 Kubernetes 运行 Docker 注册表服务器
如果你想使用 Kubernetes 启动一个私有注册表服务器,你需要有自己的 Kubernetes 集群。在本书的学习过程中,你将设置自己的 Kubernetes。如果你还没有设置,请阅读第一章《构建你的 Kubernetes 集群》,选择最简单的方式。
请注意,Docker 注册表会存储一些你的 Docker 镜像。你必须有一个 PersistentVolume 来通过 Kubernetes 管理存储。此外,由于可扩展性,我们应该预期多个 Pod 会同时读取和写入同一个 PersistentVolume。因此,你必须拥有 PersistentVolume 的 ReadWriteMany (RWX) 访问模式,例如 GlusterFS 或 NFS。
PersistentVolume 的详细信息在第二章《深入理解 Kubernetes 概念》的 工作与卷 部分进行了描述。我们来创建一个使用 NFS 的 PersistentVolume,名称为 pvnfs01,并分配 100 GB:
//my NFS server(10.138.0.5) shares /nfs directory
$ showmount -e 10.138.0.5
Export list for 10.138.0.5:
/nfs *
//please change spec.nfs.path and spec.nfs.server to yours
$ cat pv_nfs.yaml
apiVersion: "v1"
kind: "PersistentVolume"
metadata:
name: pvnfs01
spec:
capacity:
storage: "100Gi"
accessModes:
- "ReadWriteMany"
nfs:
path: "/nfs"
server: "10.138.0.5"
$ kubectl create -f pv_nfs.yaml
persistentvolume "pvnfs01" created
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvnfs01 100Gi RWX Retain Available 5s
如果你无法准备 RWX PersistentVolume,你仍然可以通过 Kubernetes 设置 Docker 注册表,但你只能启动一个 Pod(副本数:1)。作为替代,你可以使用 AWS S3 或 GCP PD 作为私有注册表的后端存储;请访问 docs.docker.com/registry/configuration/ 了解如何为你的注册表配置后端存储。
接下来,创建一个 PersistentVolumeClaim,它将 NFS PersistentVolume 和 Pod 配置解耦。我们来创建一个名为 pvc-1 的 PersistentVolumeClaim。确保 accessModes 设置为 ReadWriteMany,并且在创建后 STATUS 变为 Bound:
$ cat pvc-1.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-1
spec:
storageClassName: ""
accessModes:
- ReadWriteMany
resources:
requests:
storage: 100Gi
$ kubectl create -f pvc-1.yml
persistentvolumeclaim "pvc-1" created
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-1 Bound pvnfs01 100Gi RWX 5s
这就足以设置你的私有注册表了。它有一些前提条件;作为替代,使用公共云则简单得多。
使用 Amazon 弹性容器注册表
Amazon 弹性容器注册表 (ECR) 是作为 Amazon 弹性容器服务 (ECS) 的一部分推出的。本方案不会涉及 ECS 本身;我们只是使用 ECR 作为私有注册表。
要使用 Amazon ECR,你必须拥有 AWS 账户并在你的机器上安装 AWS CLI。具体步骤将在第六章《在 AWS 上构建 Kubernetes》中详细说明。你需要创建一个 IAM 用户,拥有 ACCESS KEY ID 和 SECRET ACCESS KEY,并且关联 AmazonEC2ContainerRegistryFullAccess 策略,这将授予你对 Amazon ECR 的完全管理员访问权限:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:*"
],
"Resource": "*"
}
]
}
然后通过 aws configure 命令配置 AWS CLI 的默认设置:
$ aws configure
AWS Access Key ID [None]: <Your AWS ACCESS KEY ID>
AWS Secret Access Key [None]: <Your AWS SECRET ACCESS KEY>
Default region name [None]: us-east-1
Default output format [None]:
然后我们可以开始使用 Amazon ECR。
使用 Google 云注册表
Google 容器注册表(cloud.google.com/container-registry/)是 GCP 的一部分。与 AWS 类似,使用 GCP 账户是必需的,还需要 Cloud SDK(cloud.google.com/sdk/),它是 GCP 中的命令行界面。关于 GCP 的更多细节将在第七章中介绍,在 GCP 上构建 Kubernetes。
在 GCP 上,我们只需要创建一个项目并为项目启用计费和容器注册表 API。否则,任何在gcloud中的操作都会显示错误:
$ gcloud container images list
ERROR: (gcloud.container.images.list) Bad status during token exchange: 403
为了启用计费和容器注册表 API,访问 GCP Web 控制台(console.cloud.google.com),导航到计费页面和容器注册表页面,然后启用这些功能。激活完成后,你可以使用gcloud container命令:
$ gcloud container images list
Listed 0 items.
现在我们可以开始使用 Google 容器注册表了。
如何操作...
我们已经完成了准备步骤。接下来,让我们一步一步地看看如何配置你的私有注册表。
使用 Kubernetes 启动私有注册表服务器
为了启动私有注册表,需要配置这些文件,以便使用适当的安全设置配置私有注册表:
-
SSL 证书
-
HTTP 密钥
-
HTTP 基本认证文件
创建自签名的 SSL 证书
有一个陷阱——人们往往在开始时设置一个没有身份验证的纯 HTTP(禁用 TLS)注册表。然后还需要配置 Docker 客户端(Kubernetes 节点),允许不安全的注册表等。这是一种不良做法,设置一个不安全环境需要很多步骤。
最好的做法是始终使用由证书授权机构颁发的官方 SSL 证书。然而,自签名证书总是非常方便,特别是在测试阶段。官方证书可以等到我们定义了 FQDN 后再使用。因此,本教程将向你展示如何使用 OpenSSL 通过以下步骤创建自签名的 SSL 证书:
- 创建
secrets目录:
$ mkdir secrets
- 运行
openssl命令,指定选项生成证书(domain.crt)和私钥(domain.key),并将其保存在 secrets 目录下。注意,你可以输入.跳过输入位置和电子邮件信息:
$ openssl req -newkey rsa:4096 -nodes -sha256 -keyout secrets/domain.key -x509 -days 365 -out secrets/domain.crt
Generating a 4096 bit RSA private key
.............................................++
...........................................................++
writing new private key to 'secrets/domain.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:us
State or Province Name (full name) []:California
Locality Name (eg, city) []:Cupertino
Organization Name (eg, company) []:packtpub
Organizational Unit Name (eg, section) []:chapter5
Common Name (eg, fully qualified host name) []:.
Email Address []:.
- 检查
secrets目录下是否生成了证书和私钥:
$ ls secrets/
domain.crt domain.key
创建 HTTP 密钥
关于 HTTP 密钥,它将在默认情况下由私有注册表实例在启动时随机生成。然而,如果你运行多个 pod,这就成了一个问题,因为每个 pod 可能有不同的 HTTP 密钥,导致 Docker 客户端推送或拉取镜像时出现错误。所以我们明确声明,所有 pod 将使用相同的 HTTP 密钥,方法如下:
- 使用
openssl命令在secrets目录下创建http.secret文件:
//create 8 byte random HEX string by OpenSSL
$ openssl rand -hex -out secrets/http.secret 8
- 检查
secrets目录,目前该目录下有三个文件:
$ ls secrets/
domain.crt domain.key http.secret
创建 HTTP 基本认证文件
最后,关于 HTTP 基本认证文件,如果你设置了私有仓库,交互时需要进行身份验证。你必须执行docker login以获取令牌,才能在推送和拉取镜像时使用。为了创建 HTTP 基本认证文件,使用由 Apache2 提供的htpasswd命令,因为这是最简单的。让我们通过以下步骤创建一个 HTTP 基本认证文件:
- 使用 Apache2 Docker 镜像(
httpd)运行 Docker,执行htpasswd命令并使用bcrypt(-B)选项生成基本认证文件(registry_passwd),该文件将位于secrets目录下:
//set user=user01, passwd=my-super-secure-password
$ docker run -i httpd /bin/bash -c 'echo my-super-secure-password | /usr/local/apache2/bin/htpasswd -nBi user01' > secrets/registry_passwd
- 检查
secrets目录,此时你应该有四个文件:
$ ls secrets/
domain.crt domain.key http.secret registry_passwd
创建 Kubernetes 密钥以存储安全文件
共有四个文件。我们使用Kubernetes Secret,使所有 Pod 能够通过环境变量访问,或者挂载卷并将其作为文件访问。有关密钥的更多详情,请参阅第二章《Kubernetes 概念详解》中的与密钥配合使用部分。你可以使用kubectl命令通过以下步骤将这四个文件加载到 Kubernetes 密钥中:
- 运行
kubectl create命令,并使用--from-file参数指定密钥目录:
$ kubectl create secret generic registry-secrets --from-file secrets/
secret "registry-secrets" created
- 通过
kubectl describe命令检查状态:
$ kubectl describe secret registry-secrets
Name: registry-secrets
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
domain.key: 3243 bytes
http.secret: 17 bytes
registry_passwd: 69 bytes
domain.crt: 1899 bytes
配置私有仓库以加载 Kubernetes 密钥
另一方面,私有仓库本身支持将 HTTP 密钥作为字符串格式的环境变量读取。它还可以支持将 SSL 证书和 HTTP 基本认证文件的文件路径指定为环境变量:
| 环境变量名称 | 描述 | 示例值 |
|---|---|---|
REGISTRY_HTTP_SECRET |
HTTP 密钥字符串 | valueFrom: secretKeyRef: name: registry-secrets key: http.secret |
REGISTRY_HTTP_TLS_CERTIFICATE |
证书文件路径(domain.crt) |
/mnt/domain.crt |
REGISTRY_HTTP_TLS_KEY |
私钥文件路径(domain.key) |
/mnt/domain.key |
REGISTRY_AUTH_HTPASSWD_REALM |
注册表服务器进行身份验证的领域 | basic-realm |
REGISTRY_AUTH_HTPASSWD_PATH |
htpasswd文件路径(registry_passwd) |
/mnt/registry_passwd |
REGISTRY_HTTP_HOST |
指定 Kubernetes 节点的 IP 和nodePort |
10.138.0.3:30500 |
理想情况下,你应该有一个负载均衡器,并将 Kubernetes 服务类型设置为LoadBalancer。然后REGISTRY_HTTP_HOST可以是负载均衡器的 IP 和端口号。为了简便起见,本示例中我们将使用NodePort。有关LoadBalancer的更多信息,请参阅第二章《Kubernetes 概念详解》中的与服务配合使用部分,以及第三章《玩转容器》中的转发容器端口部分。
我们将进行一个部署到 Kubernetes YAML 文件中,以创建一个注册表,并将前面的变量包含在其中,这样注册表的 Pod 就可以使用它们。现在我们有 PersistentVolumeClaim 作为 pvc-1,它为容器镜像存储提供支持,并通过 Secret registry-secrets 挂载 SSL 证书文件(domain.crt 和 domain.key)以及 HTTP 基本认证文件(registry_passwd)。还通过 Secret registry-secrets 读取 HTTP Secret 字符串作为环境变量。整个 YAML 配置如下:
$ cat private_registry.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-private-registry
spec:
replicas: 1
selector:
matchLabels:
run: my-registry
template:
metadata:
labels:
run: my-registry
spec:
containers:
- name: my-registry
image: registry
env:
- name: REGISTRY_HTTP_HOST
value: 10.138.0.3:30500
- name: REGISTRY_HTTP_SECRET
valueFrom:
secretKeyRef:
name: registry-secrets
key: http.secret
- name: REGISTRY_HTTP_TLS_CERTIFICATE
value: /mnt/domain.crt
- name: REGISTRY_HTTP_TLS_KEY
value: /mnt/domain.key
- name: REGISTRY_AUTH_HTPASSWD_REALM
value: basic-realm
- name: REGISTRY_AUTH_HTPASSWD_PATH
value: /mnt/registry_passwd
ports:
- containerPort: 5000
volumeMounts:
- mountPath: /var/lib/registry
name: registry-storage
- mountPath: /mnt
name: certs
volumes:
- name: registry-storage
persistentVolumeClaim:
claimName: "pvc-1"
- name: certs
secret:
secretName: registry-secrets
items:
- key: domain.key
path: domain.key
- key: domain.crt
path: domain.crt
- key: registry_passwd
path: registry_passwd
---
apiVersion: v1
kind: Service
metadata:
name: private-registry-svc
spec:
ports:
- protocol: TCP
port: 5000
nodePort: 30500
type: NodePort
selector:
run: my-registry
$ kubectl create -f private_registry.yaml
deployment.apps "my-private-registry" created
service "private-registry-svc" created
//can scale to multiple Pod (if you have RWX PV set)
$ kubectl scale deploy my-private-registry --replicas=3
deployment "my-private-registry" scaled
$ kubectl get deploy
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
my-private-registry 3 3 3 3 2m
现在你自己的私有注册表已经准备好使用了!
在 AWS 弹性容器注册表上创建仓库
为了将容器镜像推送到 Amazon ECR,你需要提前创建一个仓库。与 Docker Hub 或私有注册表不同,Amazon ECR 在第一次推送镜像时不会自动创建仓库。因此,如果你想推送三个容器镜像,你需要提前创建三个仓库:
输入 aws ecr create-repository 命令来指定仓库名称是很简单的:
$ aws ecr create-repository --repository-name my-nginx
{
"repository": {
"registryId": "************",
"repositoryName": "my-nginx",
"repositoryArn": "arn:aws:ecr:us-east-1:************:repository/my-nginx",
"createdAt": 1516608220.0,
"repositoryUri": "************.dkr.ecr.us-east-1.amazonaws.com/my-nginx"
}
}
就这样!你需要记住 repositoryUri(在前面的例子中是 ************.dkr.ecr.us-east-1.amazonaws.com/my-nginx),它将作为私有镜像 URL 使用。
上述 URL 被掩码为 ID ************,它与您的 AWS 账户 ID 绑定。
另一方面,如果你看到如下错误信息,说明你的 IAM 用户没有 CreateRepository 操作的权限。在这种情况下,你需要附加 AmazonEC2ContainerRegistryFullAccess 的 IAM 策略:
$ aws ecr create-repository --repository-name chapter5
An error occurred (AccessDeniedException) when calling the CreateRepository operation: User: arn:aws:iam::************:user/ecr-user is not authorized to perform: ecr:CreateRepository on resource: *
确定在 Google 容器注册表上的仓库 URL
为了将容器镜像推送到 Google 容器注册表,需要考虑仓库 URL 的一个重要问题。首先,有多个 Google 容器注册表区域主机可用:
-
gcr.io(当前为美国地区) -
us.gcr.io(美国地区) -
eu.gcr.io(欧洲地区) -
asia.gcr.io(亚洲地区)
请注意,这些区域主机是为了网络延迟优化,并不意味着限制在特定区域内。它们在全球范围内仍然可访问。
其次,在为容器镜像打标签时,你还需要指定你已启用计费和 API 的 project-id。因此,整个仓库 URL 可能是:
<gcr region>/<project-id>/<image name>:tag
在我的例子中,我使用了默认的美国区域,项目 ID 为 kubernetes-cookbook,镜像名称为 my-nginx;因此,我的仓库 URL 是:
gcr.io/kubernetes-cookbook/my-nginx:latest
除此之外,Google 容器注册表现在已准备就绪!
它是如何工作的...
当你开始在 Kubernetes 中使用私有注册表时,必须正确配置凭证。Amazon ECR 和 Google Cloud 注册表需要特别的考虑。让我们为私有注册表、Amazon ECR 和 Google Cloud 注册表配置凭证。
从你的私有注册表推送和拉取镜像
现在你可以将容器镜像推送到你的私有仓库。因为我们已经设置了 HTTP 基本认证,所以需要先进行 docker login。否则,你会遇到 no basic auth credentials 错误:
//just tag nginx to your own private image
$ docker tag nginx 10.138.0.3:30500/my-nginx
//will be failed when push without login information. using complete image name with private registry as prefix
$ docker push 10.138.0.3:30500/my-nginx
The push refers to a repository [10.138.0.3:30500/my-nginx]
a103d141fc98: Preparing
73e2bd445514: Preparing
2ec5c0a4cb57: Preparing
no basic auth credentials
因此,你需要使用 docker login 来指定用户名和密码,这些密码会被设置到 registry_passwd 文件中:
//docker login
$ docker login 10.138.0.3:30500
Username: user01
Password:
Login Succeeded
//successfully to push
$ docker push 10.138.0.3:30500/my-nginx
The push refers to a repository [10.138.0.3:30500/my-nginx]
a103d141fc98: Pushed
73e2bd445514: Pushed
2ec5c0a4cb57: Pushed
latest: digest: sha256:926b086e1234b6ae9a11589c4cece66b267890d24d1da388c96dd8795b2ffcfb size: 948
另一方面,对于从私有仓库拉取镜像,Kubernetes 节点也需要为你的私有仓库提供凭证。但在每个节点上使用 docker login 命令并不现实。相反,Kubernetes 支持将凭证存储为 Kubernetes 秘密,并且每个节点在拉取镜像时都会使用这些凭证。
为此,我们需要创建一个 docker-registry 资源,该资源需要指定:
-
--docker-server:在本示例中,10.138.0.3:30500 -
--docker-username:在本示例中,user01 -
--docker-password:在本示例中,my-super-secure-password -
--docker-email:你的电子邮件地址
//create secret named "my-private-credential"
$ kubectl create secret docker-registry my-private-credential \
> --docker-server=10.138.0.3:30500 \
> --docker-username=user01 \
> --docker-password=my-super-secure-password \
> --docker-email=hideto.saito@example.com
secret "my-private-credential" created
//successfully to created
$ kubectl get secret my-private-credential
NAME TYPE DATA AGE
my-private-credential kubernetes.io/dockerconfigjson 1 18s
最后,你可以从指定 my-private-credential 秘密的私有仓库中拉取私有镜像。为此,设置 spec.imagePullSecrets 如下:
$ cat private-nginx.yaml
apiVersion: v1
kind: Pod
metadata:
name: private-nginx
spec:
containers:
- name: private-nginx
image: 10.138.0.3:30500/my-nginx
imagePullSecrets:
- name: my-private-credential
$ kubectl create -f private-nginx.yaml
pod "private-nginx" created
//successfully to launch your Pod using private image
$ kubectl get pods private-nginx
NAME READY STATUS RESTARTS AGE
private-nginx 1/1 Running 0 10s
恭喜!现在你可以自由地将私有镜像推送到由 Kubernetes 运行的私有仓库中。同时,也可以从 Kubernetes 中拉取镜像。在任何时候,你都可以根据客户端流量进行横向扩展。
从 Amazon ECR 推送和拉取镜像
Amazon ECR 具有身份验证机制,用于提供对私有仓库的访问。AWS CLI 提供了一个功能,可以通过 aws ecr get-login 命令生成访问令牌:
$ aws ecr get-login --no-include-email
它会输出带有 ID 和密码的 docker login 命令:
docker login -u AWS -p eyJwYXlsb2FkIjoiNy(very long strings)... https://************.dkr.ecr.us-east-1.amazonaws.com
因此,只需将命令复制并粘贴到终端中,即可从 AWS 获取令牌。然后尝试使用 docker push 将 Docker 镜像上传到 ECR:
$ docker tag nginx ************.dkr.ecr.us-east-1.amazonaws.com/my-nginx
$ docker push ************.dkr.ecr.us-east-1.amazonaws.com/my-nginx
The push refers to repository [************.dkr.ecr.us-east-1.amazonaws.com/my-nginx]
a103d141fc98: Pushed
73e2bd445514: Pushing 8.783MB/53.23MB
2ec5c0a4cb57: Pushing 4.333MB/55.26MB
另一方面,从 ECR 拉取镜像到 Kubernetes 的步骤与使用 Kubernetes 秘密存储令牌的私有仓库完全相同:
$ kubectl create secret docker-registry my-ecr-secret \
> --docker-server=https://************.dkr.ecr.us-east-1.amazonaws.com \
> --docker-email=hideto.saito@example.com \
> --docker-username=AWS \
> --docker-password=eyJwYXlsb2FkIjoiS...
secret "my-ecr-secret" created
$ kubectl get secret my-ecr-secret
NAME TYPE DATA AGE
my-ecr-secret kubernetes.io/dockerconfigjson 1 10s
现在,spec.imagePullSecrets 需要指定 my-ecr-secret。除了镜像 URL,它还指定了 ECR 仓库:
$ cat private-nginx-ecr.yaml
apiVersion: v1
kind: Pod
metadata:
name: private-nginx-ecr
spec:
containers:
- name: private-nginx-ecr
image: ************.dkr.ecr.us-east-1.amazonaws.com/my-nginx
imagePullSecrets:
- name: my-ecr-secret
$ kubectl create -f private-nginx-ecr.yaml
pod "private-nginx-ecr" created
$ kubectl get pods private-nginx-ecr
NAME READY STATUS RESTARTS AGE
private-nginx-ecr 1/1 Running 0 1m
请注意,这个令牌是短期有效的:它的有效期为 12 小时。因此,12 小时后,你需要再次运行 aws ecr get-login 来获取新的令牌,然后更新 my-ecr-secret 秘密。这样做显然不是最理想的。
好消息是,Kubernetes 支持通过 CloudProvider 自动更新 ECR 令牌。然而,它要求你的 Kubernetes 运行在 AWS 环境中,如 EC2 实例上。此外,EC2 实例需要拥有与 AmazonEC2ContainerRegistryReadOnly 策略相同或更高权限的 IAM 角色。这将在 第六章 中描述,在 AWS 上构建 Kubernetes。
如果你真的想在 AWS 之外使用 Kubernetes 集群,通过从 ECR 仓库拉取镜像,面临的挑战是你需要每 12 小时更新一次 ECR 令牌。也许你可以通过定时任务(cron job)或者采用一些自动化工具来实现这一点。
欲了解更多详情,请访问 AWS 在线文档 docs.aws.amazon.com/AmazonECR/latest/userguide/Registries.html。
从 Google 云注册表推送和拉取镜像
根据 GCP 文档 (cloud.google.com/container-registry/docs/advanced-authentication),有几种方法可以推送/拉取到容器注册表。
使用 gcloud 封装 Docker 命令
gcloud 命令具有一个封装功能,可以运行 docker 命令来推送和拉取。例如,如果你想推送镜像 gcr.io/kubernetes-cookbook/my-nginx,可以使用 gcloud 命令:
$ gcloud docker -- push gcr.io/kubernetes-cookbook/my-nginx
从你的机器推送镜像已足够,但如果与 Kubernetes 集成则不理想。这是因为在 Kubernetes 节点上封装 gcloud 命令并不容易。
幸运的是,有一个解决方案可以创建一个 GCP 服务账户并授予它权限(角色)。
使用 GCP 服务账户授予长期凭证
我们需要集成以从 Kubernetes 节点拉取镜像,这需要一个长期凭证,可以将其存储到 Kubernetes 密钥中。为此,请执行以下步骤:
- 创建一个 GCP 服务账户(
container-sa):
$ gcloud iam service-accounts create container-sa
Created service account [container-sa].
//full name is as below
$ gcloud iam service-accounts list | grep container
container-sa@kubernetes-cookbook.iam.gserviceaccount.com
- 将
container-sa(使用全名)分配给roles/storage.admin角色:
$ gcloud projects add-iam-policy-binding kubernetes-cookbook \
> --member serviceAccount:container-sa@kubernetes-cookbook.iam.gserviceaccount.com \
> --role=roles/storage.admin
- 为
container-sa生成一个密钥文件 (container-sa.json):
$ gcloud iam service-accounts keys create container-sa.json \
> --iam-account container-sa@kubernetes-cookbook.iam.gserviceaccount.com
created key [f60a81235a1ed9fbce881639f621470cb087149c] of type [json] as [container-sa.json] for [container-sa@kubernetes-cookbook.iam.gserviceaccount.com]
- 使用
docker login检查密钥文件是否有效:
//note that username must be _json_key
$ cat container-sa.json | docker login --username _json_key --password-stdin gcr.io
Login Succeeded
- 使用
docker pull检查是否可以从容器注册表拉取:
$ docker pull gcr.io/kubernetes-cookbook/my-nginx
Using default tag: latest
latest: Pulling from kubernetes-cookbook/my-nginx
e7bb522d92ff: Pulling fs layer
6edc05228666: Pulling fs layer
...
一切看起来都很完美!现在你可以像使用私人注册表或 AWS ECR 一样使用 Kubernetes 密钥。
- 创建一个 Kubernetes 密钥 (
my-gcr-secret),以指定_json_key和container-sa.json:
$ kubectl create secret docker-registry my-gcr-secret \
> --docker-server=gcr.io \
> --docker-username=_json_key \
> --docker-password=`cat container-sa.json` \
> --docker-email=hideto.saito@example.com
secret "my-gcr-secret" created
- 将
my-gcr-secret指定给imagePullSecrets以启动一个 pod:
$ cat private-nginx-gcr.yaml
apiVersion: v1
kind: Pod
metadata:
name: private-nginx-gcr
spec:
containers:
- name: private-nginx-gcr
image: gcr.io/kubernetes-cookbook/my-nginx
imagePullSecrets:
- name: my-gcr-secret
$ kubectl create -f private-nginx-gcr.yaml
pod "private-nginx-gcr" created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
private-nginx-gcr 1/1 Running 0 47s
恭喜!现在你可以使用 Google 容器注册表作为完全由 GCP 管理的私人注册表,Kubernetes 可以从那里拉取你的私人镜像。
与 Jenkins 集成
在软件工程中,持续集成(CI) (en.wikipedia.org/wiki/Continuous_integration) 和 持续交付(CD) (en.wikipedia.org/wiki/Continuous_delivery),简称 CI/CD,具有简化传统开发流程的能力,通过持续开发、测试和交付机制,减少严重冲突的恐慌,即一次交付小的变更,并能立即缩小问题范围(如果有的话)。此外,通过自动化工具,CI/CD 系统交付的产品可以提高效率并缩短市场时间。
Jenkins 是一个著名的 CI 系统,可以配置为持续交付系统。Jenkins 可以从源代码控制系统拉取你的项目代码,运行测试,然后根据你的配置进行部署。在本食谱中,我们将向你展示如何将 Jenkins 与 Kubernetes 集成以实现持续交付。
准备工作
在开始本食谱之前,准备一个 Docker hub 账户(hub.docker.com),或者你也可以使用前一节描述的私有注册表。但重要的是,你必须有拉取和推送到注册表的凭据。如果使用 Docker hub,请确保 docker login 使用你的凭据能够正常工作。
接下来,确保你的 Kubernetes 已经准备就绪。我们将使用 RBAC 认证来访问 Jenkins pod 到 Kubernetes master API。如果你使用 minikube,启动 minikube 时需要添加 --extra-config=apiserver.Authorization.Mode=RBAC 选项:
//enable RBAC and allocate 8G memory
$ minikube start --memory=8192 --extra-config=apiserver.Authorization.Mode=RBAC
然后,你可以通过 Kubernetes 设置自己的 Jenkins 服务器;具体细节请见本节。
一些 minikube 版本存在 kube-dns 问题,无法解析外部域名,如 github.com/ 和 jenkins.io/,导致无法执行本食谱。可以通过以下命令将 kube-dns 插件替换为 coredns 插件来解决该问题:
$ minikube addons disable kube-dns
$ minikube addons enable coredns
如何操作...
在 Jenkins 设置中有两个重要部分需要处理:
-
Jenkins 需要运行
docker命令来构建你的应用程序以组成容器镜像。 -
Jenkins 需要与 Kubernetes master 通信以控制部署。
为了实现步骤 1,有一个棘手的部分需要类似于 Docker-in-Docker(dind)的解决方案。这是因为 Jenkins 作为一个 pod(Docker 容器)由 Kubernetes 运行,Jenkins 还需要调用 docker 命令来构建你的应用程序。可以通过将 Kubernetes 节点的 /var/run/docker.sock 挂载到可以与 Jenkins、Kubernetes 节点和 Docker 守护进程通信的 Jenkins pod 来实现。
Docker-in-Docker 和挂载 /var/run/docker.sock 的方案已在 blog.docker.com/2013/09/docker-can-now-run-within-docker/ 和 jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/ 中有所描述。
为了实现步骤 2,我们将设置一个 Kubernetes 服务账户并分配一个 ClusterRole,以便 Jenkins 服务账户能够获得必要的权限。
让我们一步步来。
设置自定义的 Jenkins 镜像
使用 Kubernetes 运行 Jenkins,我们使用官方镜像(hub.docker.com/u/jenkins/),但对其进行自定义以安装以下应用程序:
-
Docker CE
-
kubectl 二进制文件
-
Jenkins Docker 插件
为此,准备一个 Dockerfile 来维护你自己的 Jenkins 镜像:
$ cat Dockerfile
FROM jenkins/jenkins:lts
EXPOSE 8080 50000
# install Docker CE for Debian : https://docs.docker.com/engine/installation/linux/docker-ce/debian/
USER root
RUN apt-get update
RUN apt-get install -y sudo apt-transport-https ca-certificates curl gnupg2 software-properties-common
RUN curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg | apt-key add -
RUN add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") $(lsb_release -cs) stable"
RUN apt-get update && apt-get install -y docker-ce
# install kubectl binary
RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.9.2/bin/linux/amd64/kubectl
RUN chmod +x ./kubectl
RUN mv ./kubectl /usr/local/bin/kubectl
# setup Jenkins plubins : https://github.com/jenkinsci/docker#script-usage
RUN /usr/local/bin/install-plugins.sh docker
使用 docker build 构建你的 Jenkins 镜像,然后使用 docker push 命令上传到你自己的 Docker Hub 注册中心,如下所示:
//build your own Jenkins image
$ docker build -t <your-docker-hub-account>/my-jenkins .
//push to Docker Hub
$ docker push <your-docker-hub-account>/my-jenkins
或者,你也可以将其上传到你的私有注册中心或任何其他云提供的注册中心。
好极了!现在我们的构建系统镜像已经准备好了。
设置 Kubernetes 服务账户和 ClusterRole
想象一下,在成功使用 Jenkins 构建了你的应用容器后,你随后使用 kubectl 更新部署以发布新的二进制文件。为了做到这一点,你需要从 Jenkins pod 内部调用 kubectl 命令。在这种情况下,我们需要凭证来与 Kubernetes 主节点进行通信。
幸运的是,Kubernetes 支持这种场景,使用服务账户来完成。详细说明可以在 第八章,高级集群管理 中找到。所以,本示例将使用最简单的方式,使用 default 命名空间和 cluster-admin ClusterRole。
要检查是否启用了 RBAC,并且 cluster-admin ClusterRole 是否存在,可以输入 kubectl get clusterrole 命令:
$ kubectl get clusterrole cluster-admin
NAME AGE
cluster-admin 42m
接下来,创建一个服务账户 jenkins-sa,该账户将由 Jenkins pod 使用。准备以下 YAML 配置,并输入 kubectl create 命令来创建:
$ cat jenkins-serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins-sa
namespace: default
$ kubectl create -f jenkins-serviceaccount.yaml
serviceaccount "jenkins-sa" created
现在我们可以将 jenkins-sa 服务账户与 cluster-admin ClusterRole 关联。准备一个 ClusterRoleBinding 配置并运行 kubectl create 命令:
$ cat jenkins-cluteradmin.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: jenkins-cluster-admin
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: jenkins-sa
namespace: default
$ kubectl create -f jenkins-cluster-admin.yaml
clusterrolebinding.rbac.authorization.k8s.io "jenkins-cluster-admin" created
结果是,如果使用服务账户 jenkins-sa 启动了一个 pod,那么该 Pod 拥有控制 Kubernetes 集群的权限,因为它具有 cluster-admin ClusterRole。
它应该会创建一个自定义的 ClusterRole,该角色具有 Jenkins 使用所需的最小权限。但本示例的重点是 Jenkins 的设置。如果你想创建自定义的 ClusterRole,请参阅 第八章,高级集群管理。
通过 Kubernetes 部署启动 Jenkins 服务器
基于前面的示例,现在你有了:
-
一个自定义的 Jenkins 容器镜像
-
一个服务账户
最后,你可以在 Kubernetes 集群上启动自定义的 Jenkins 服务器。记住,我们需要在 Docker 环境中运行 docker 命令,这需要从本地 Kubernetes 节点挂载 /var/run/docker.sock。
此外,我们还需要使用 jenkins-sa 服务账户来启动 Jenkins pod。在部署配置中,需要指定 spec.template.spec.serviceAccountName: jenkins-sa。
还建议使用一个 PersistentVolume 来保存 Jenkins 主目录(/var/jenkins_home),以防 pod 被重启。我们简单地使用 hostPath /data/jenkins-data 目录(假设你使用的是 minikube)。你可以根据你的环境更改为其他路径或其他类型的 PersistentVolume。
总体而言,Jenkins 的部署 YAML 配置如下:
$ cat jenkins.yaml
apiVersion: apps/v1
kind: Deployment
...
spec:
serviceAccountName: jenkins-sa
containers:
- name: my-jenkins
image: hidetosaito/my-jenkins
readinessProbe:
initialDelaySeconds: 40
tcpSocket:
port: 8080
volumeMounts:
- mountPath: /var/run/docker.sock
name: docker-sock
- mountPath: /var/jenkins_home
name: jenkins-data
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
- name: jenkins-data
hostPath:
path: /data/jenkins-data
...
$ kubectl create -f jenkins.yaml
deployment.apps "my-jenkins" created
service "my-jenkins-service" created
几分钟后,Kubernetes 会拉取你的自定义 Jenkins 镜像并运行一个 Jenkins pod,该 pod 能够运行 docker 命令和 kubectl 命令,无需任何配置,因为已经挂载了 /var/run/docker.sock 和 jenkins-sa 服务账户:
//check Jenkins Pod status
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
my-jenkins-758b89849c-t2sm9 1/1 Running 0 17m
//access to Jenkins Pod
$ kubectl exec -it my-jenkins-758b89849c-t2sm9 -- /bin/bash
//within Jenkins Pod, you can run docker command
root@my-jenkins-758b89849c-t2sm9:/# docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
e7bb522d92ff: Pull complete
6edc05228666: Pull complete
cd866a17e81f: Pull complete
Digest: sha256:926b086e1234b6ae9a11589c4cece66b267890d24d1da388c96dd8795b2ffcfb
Status: Downloaded newer image for nginx:latest
//within Jenkins Pod, you can run kubectl command
root@my-jenkins-758b89849c-t2sm9:/# kubectl get nodes
NAME STATUS ROLES AGE VERSION
gke-chapter5-default-pool-97f6cad9-19vm Ready <none> 1h v1.8.6-gke.0
gke-chapter5-default-pool-97f6cad9-1qxc Ready <none> 1h v1.8.6-gke.0
gke-chapter5-default-pool-97f6cad9-cglm Ready <none> 1h v1.8.6-gke.0
//go back to your terminal
root@my-jenkins-758b89849c-t2sm9:/# exit
exit
一切就绪!现在你可以配置 Jenkins 作业来构建你的应用程序,构建容器并部署到 Kubernetes。
它是如何工作的……
现在我们开始配置 Jenkins 来构建你的应用程序。然而,为了访问你自定义 Jenkins 的 WebUI,你需要访问绑定到 Jenkins pod 的 Kubernetes 服务。使用 kubectl port-forward 远程访问来配置 Jenkins 会更方便:
//check pod name
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
my-jenkins-cbdd6446d-ttxj5 1/1 Running 0 1m
//port forward from your machine :58080 to Jenkins :8080
$ kubectl port-forward my-jenkins-cbdd6446d-ttxj5 58080:8080
Forwarding from 127.0.0.1:58080 -> 8080
Jenkins 的初始配置通过以下步骤完成:
-
访问
http://127.0.0.1:58080Jenkins WebUI,它会要求你输入initialAdminPassword。 -
使用
kubectl exec获取initialAdminPassword。然后将其复制并粘贴到 Jenkins WebUI 进行初始配置,安装推荐的插件并创建一个管理员用户:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
my-jenkins-cbdd6446d-ttxj5 1/1 Running 0 1m
//now you see initialAdminPassword
$ kubectl exec my-jenkins-cbdd6446d-ttxj5 -- /bin/bash -c 'cat /var/jenkins_home/secrets/initialAdminPassword'
47e236f0bf334f838c33f80aac206c22
- 你将看到 Jenkins 的首页。然后点击“管理 Jenkins”,再点击“配置系统”:

导航到 Jenkins 配置
- 滚动到页面底部,找到云部分。点击“添加新云”并选择 Docker:

添加 Docker 设置
- 将名称设置为你想要的名称(例如:
my-docker),并指定 Docker 主机 URI 和 Docker 域套接字为unix:///var/run/docker.sock:

配置 Jenkins 上的 Docker
使用 Jenkins 构建 Docker 镜像
让我们配置一个 Jenkins 作业来构建一个示例微服务应用程序,该应用程序在前面的配方中有介绍(my-calc)。执行以下步骤来配置和构建 Docker 镜像:
- 在左侧导航栏中,点击“新建项目”:

导航到创建新项目
- 输入你想要的项目名称(例如:
my-calc),选择“自由风格项目”,然后点击“确定”:

创建一个新的 Jenkins 作业
- 在源代码管理选项卡中,选择 Git 并将仓库 URL 设置为
github.com/kubernetes-cookbook/my-calc.git,或者你也可以使用你自己的仓库,其中包含Dockerfile:

源代码管理设置
- 在构建环境选项卡中,点击“添加构建步骤”以添加构建/发布 Docker 镜像:

构建环境设置
-
在“构建/发布 Docker 镜像”面板中:
-
将
Dockerfile的目录设置为当前目录(.) -
选择我们设置的云中的 my-docker
-
将镜像地址设置为你的 Docker 仓库,但在末尾添加
:${BUILD_NUMBER}(例如:hidetosaito/my-calc:${BUILD_NUMBER}) -
启用推送镜像
-
点击“添加”以添加你的 Docker hub ID 凭证
-
然后点击“保存”:
-

Docker 构建/发布设置
- 最后,你可以点击“立即构建”来触发构建;为了测试,你可以点击五次看看它是如何工作的:

触发构建
- 注意,你可以看到一个控制台,它知道正在执行 Docker 构建和推送:

显示构建日志
- 访问你的 Docker hub 仓库;它已经被推送了五次(因为点击了五次构建):

Docker hub 仓库
就这样!你可以通过持续集成构建 Docker 镜像,当你在 GitHub 上更新源代码时,Jenkins 可以持续构建并将最新的镜像推送到你的 Docker hub 仓库。
将最新的容器镜像部署到 Kubernetes
每次构建后,Jenkins 都会在 CI 流程结束时将你的容器镜像推送到 Docker hub 仓库。接下来,更新 Jenkins 任务配置,使用最新镜像部署到 Kubernetes,步骤如下:
- 第一次,我们通过
kubectl deploy --record手动预部署了微服务应用。请注意,你可能需要将spec.template.spec.containers.image: hidetosaito/my-calc修改为你自己的仓库:
$ cat my-calc.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-calc-deploy
spec:
replicas: 2
selector:
matchLabels:
run: my-calc
template:
metadata:
labels:
run: my-calc
spec:
containers:
- name: my-calc
image: hidetosaito/my-calc
//use --record to trace the history
$ kubectl create -f my-calc-deploy.yaml --record
deployment.apps "my-calc-deploy" created
- 打开 Jenkins 任务配置;在构建选项卡上,点击 Docker 构建设置后的“添加构建步骤”,然后选择“执行 Shell”:

添加构建步骤
- 添加这个 Shell 脚本并点击保存:
#!/bin/sh
set +x
# These 2 are defined in Deployment YAML
DEPLOYMENT_NAME=my-calc-deploy
CONTAINER_NAME=my-calc
# change to your Docker Hub repository
REPOSITORY=hidetosaito/my-calc
echo "*********************"
echo "*** before deploy ***"
echo "*********************"
kubectl rollout history deployment $DEPLOYMENT_NAME
kubectl set image deployment $DEPLOYMENT_NAME $CONTAINER_NAME=$REPOSITORY:$BUILD_NUMBER
echo "******************************************"
echo "*** waiting to complete rolling update ***"
echo "******************************************"
kubectl rollout status --watch=true deployment $DEPLOYMENT_NAME
echo "********************"
echo "*** after deploy ***"
echo "********************"
kubectl rollout history deployment $DEPLOYMENT_NAME
- 触发一次新构建;你可以看到在 Docker push 后,它会运行前面的脚本:

Kubernetes 部署结果
现在你可以将持续集成扩展到持续交付!你可以在上述脚本中增加单元测试、集成测试和回滚机制,以增强你的 CI/CD 流程。
第六章:在 AWS 上构建 Kubernetes
本章涵盖以下内容:
-
玩转 Amazon Web Services
-
使用 kops 设置 Kubernetes
-
使用 AWS 作为 Kubernetes 云提供商
-
使用 kops 管理 AWS 上的 Kubernetes 集群
介绍
根据云原生计算基金会(CNCF)最近的调查,Amazon Web Services(AWS)是生产级 Kubernetes 系统的主流解决方案 (www.cncf.io/blog/2017/12/06/cloud-native-technologies-scaling-production-applications/)。在本章中,您将了解 AWS 的云服务,以及这些服务如何协同工作以提供一个强大的 Kubernetes 系统。我们还将介绍 Kubernetes 运维工具 kops 的工作原理,帮助我们管理 Kubernetes 集群。让我们一起探索 AWS 上的 Kubernetes 世界!
玩转 Amazon Web Services
Amazon Web Services (aws.amazon.com) 是最受欢迎的公共云服务。它提供虚拟服务器(EC2)、软件定义网络(VPC)、对象存储(S3)等在线服务。它是搭建 Kubernetes 集群的合适基础设施。我们将探索 AWS,了解其基础功能。
准备工作
首先,您需要注册 AWS。AWS 提供免费的层级服务,允许您在 12 个月内免费使用一定数量的 AWS 资源。请访问 aws.amazon.com/free/ 注册您的信息和信用卡。验证和激活您的账户可能需要 24 小时。
一旦您的 AWS 账户激活,我们需要创建一个身份与访问管理(IAM)用户,该用户将通过 API 控制您的 AWS 基础设施。然后,安装 AWS CLI 到您的计算机上。
创建一个 IAM 用户
执行以下步骤创建一个 IAM 用户:
-
访问 AWS Web 控制台
console.aws.amazon.com。 -
点击 IAM(使用搜索框,这样更容易找到):

访问 IAM 控制台
- 在左侧导航栏中点击 Users,然后点击 Add user:

创建一个 IAM 用户
- 输入用户名
chap6,然后选择程序访问:

创建 chap6 用户
-
选择“直接附加现有策略”(如以下截图所示),然后选择以下策略:
-
AmazonEC2FullAccess
-
AmazonRoute53FullAcccess
-
AmazonS3FullAccess
-
AmazonVPCFullAccess
-
IAMFullAccess
-

附加必要的策略
- 最终,它会生成访问密钥 ID 和秘密访问密钥。复制并粘贴到您的文本编辑器中,或者点击下载 .csv 以保存到您的计算机:

下载访问密钥 ID 和秘密访问密钥
在 macOS 上安装 AWS CLI
使用 HomeBrew 在 macOS 上安装 awscli(brew.sh);这是最简单的方法。HomeBrew 已经在 第一章中介绍过,构建你自己的 Kubernetes 集群,同时安装 minikube。
在你的 Mac 上通过 HomeBrew 安装 awscli,执行以下步骤:
- 输入以下命令以更新最新的公式:
$ brew update
- 指定安装
awscli:
$ brew install awscli
- 使用
--version选项验证aws命令:
$ aws --version
aws-cli/1.15.0 Python/3.6.5 Darwin/17.5.0 botocore/1.10.0
在 Windows 上安装 AWS CLI
在 Windows 上安装 awscli;有一个 Windows 安装程序包,这是在 Windows 上安装 awscli 的最简单方法:
-
访问 AWS 命令行接口页面(
aws.amazon.com/cli/)。 -
下载 Windows 安装程序 64 位(
s3.amazonaws.com/aws-cli/AWSCLI64.msi)或 32 位(s3.amazonaws.com/aws-cli/AWSCLI32.msi),取决于你的 Windows 操作系统。 -
启动 AWS CLI 安装程序,然后选择默认选项继续安装:

在 Windows 上安装 AWS CLI
- 安装完成后,启动命令提示符。然后,输入
aws命令并使用--version选项进行验证:

在 Windows 上显示 aws 命令
如何操作...
首先,你需要为 awscli 设置你的 AWS Access Key ID 和 AWS Secret Access Key。我们已经为 IAM 用户获取了 chap6。我们将使用该用户的 Access Key ID 和 Secret Access Key。
- 启动终端(Windows 的命令提示符),然后使用
aws命令设置Access Key ID和Secret Access Key。同时,设置默认区域为us-east-1:
$ aws configure
AWS Access Key ID [None]: <Your Access KeyID>
AWS Secret Access Key [None]: <Your Secret Access Key>
Default region name [None]: us-east-1
Default output format [None]:
- 使用以下命令检查
chap6IAM 用户:
$ aws iam get-user
{
"User": {
"Path": "/",
"UserName": "chap6",
"UserId": "*********************",
"Arn": "arn:aws:iam::***************:user/chap6",
"CreateDate": "2018-04-14T04:22:21Z"
}
}
就这样!现在你可以开始使用 AWS 启动自己的网络和实例。
它是如何工作的...
让我们探索 AWS 来启动一个典型的基础设施。使用 awscli 构建自己的 VPC、子网、网关和安全组。然后,启动 EC2 实例,了解 AWS 的基本使用。
创建 VPC 和子网
虚拟私有云 (VPC) 是一种软件定义的网络。你可以在 AWS 上配置一个虚拟网络。子网位于 VPC 内,定义了网络块(无类域间路由(CIDR)),例如 192.168.1.0/24。
让我们通过以下步骤创建一个 VPC 和两个子网:
- 创建一个新的 VPC,CIDR 块为
192.168.0.0/16(IP 范围:192.168.0.0–192.168.255.255)。然后,捕获VpcId:
$ aws ec2 create-vpc --cidr-block 192.168.0.0/16
{
"Vpc": {
"CidrBlock": "192.168.0.0/16",
"DhcpOptionsId": "dopt-3d901958",
"State": "pending",
"VpcId": "vpc-69cfbd12",
"InstanceTenancy": "default",
"Ipv6CidrBlockAssociationSet": [],
"CidrBlockAssociationSet": [
{
"AssociationId": "vpc-cidr-assoc-c35411ae",
"CidrBlock": "192.168.0.0/16",
"CidrBlockState": {
"State": "associated"
}
}
],
"IsDefault": false,
"Tags": []
}
}
- 在 VPC (
vpc-69cfbd12) 下创建第一个子网,该子网有192.168.0.0/24的 CIDR 块(IP 范围:192.168.0.0–192.168.0.255),并指定可用区为us-east-1a。然后,捕获SubnetId:
$ aws ec2 create-subnet --vpc-id vpc-69cfbd12 --cidr-block 192.168.0.0/24 --availability-zone us-east-1a
{
"Subnet": {
"AvailabilityZone": "us-east-1a",
"AvailableIpAddressCount": 251,
"CidrBlock": "192.168.0.0/24",
"DefaultForAz": false,
"MapPublicIpOnLaunch": false,
"State": "pending",
"SubnetId": "subnet-6296863f",
"VpcId": "vpc-69cfbd12",
"AssignIpv6AddressOnCreation": false,
"Ipv6CidrBlockAssociationSet": []
}
}
- 在
us-east-1b上创建第二个子网,CIDR 块为192.168.1.0/24(IP 范围:192.168.1.0–192.168.1.255)。然后,捕获SubnetId:
$ aws ec2 create-subnet --vpc-id vpc-69cfbd12 --cidr-block 192.168.1.0/24 --availability-zone us-east-1b
{
"Subnet": {
"AvailabilityZone": "us-east-1b",
"AvailableIpAddressCount": 251,
"CidrBlock": "192.168.1.0/24",
"DefaultForAz": false,
"MapPublicIpOnLaunch": false,
"State": "pending",
"SubnetId": "subnet-ce947da9",
"VpcId": "vpc-69cfbd12",
"AssignIpv6AddressOnCreation": false,
"Ipv6CidrBlockAssociationSet": []
}
}
- 使用以下命令检查 VPC(
vpc-69cfbd12)下的子网列表:
$ aws ec2 describe-subnets --filters "Name=vpc-id,Values=vpc-69cfbd12" --query "Subnets[*].{Vpc:VpcId,CIDR:CidrBlock,AZ:AvailabilityZone,Id:SubnetId}" --output=table
---------------------------------------------------------------------
| DescribeSubnets |
+------------+------------------+-------------------+---------------+
| AZ | CIDR | Id | Vpc |
+------------+------------------+-------------------+---------------+
| us-east-1a| 192.168.0.0/24 | subnet-6296863f | vpc-69cfbd12 |
| us-east-1b| 192.168.1.0/24 | subnet-ce947da9 | vpc-69cfbd12 |
+------------+------------------+-------------------+---------------+
看起来不错!
Internet 网关
要访问您的 VPC 网络,您需要有一个从互联网访问它的网关。Internet 网关(IGW)就是将互联网连接到您的 VPC 的网关。
然后,在 VPC 下的子网中,您可以设置默认路由是否指向 IGW。如果指向 IGW,该子网将被归类为公共子网。然后,您可以在公共子网上分配全球 IP 地址。
让我们通过以下步骤配置第一个子网(192.168.0.0/24)作为指向 IGW 的公共子网:
- 创建 IGW 并捕获
InternetGatewayId:
$ aws ec2 create-internet-gateway
{
"InternetGateway": {
"Attachments": [],
"InternetGatewayId": "igw-e50b849d",
"Tags": []
}
}
- 将 IGW(
igw-e50b849d)附加到您的 VPC(vpc-69cfbd12)上:
$ aws ec2 attach-internet-gateway --vpc-id vpc-69cfbd12 --internet-gateway-id igw-e50b849d
- 在 VPC(
vpc-69cfbd12)上创建路由表,然后捕获RouteTableId:
$ aws ec2 create-route-table --vpc-id vpc-69cfbd12
{
"RouteTable": {
"Associations": [],
"PropagatingVgws": [],
"RouteTableId": "rtb-a9e791d5",
"Routes": [
{
"DestinationCidrBlock": "192.168.0.0/16",
"GatewayId": "local",
"Origin": "CreateRouteTable",
"State": "active"
}
],
"Tags": [],
"VpcId": "vpc-69cfbd12"
}
}
- 将路由表(
rtb-a9e791d5)的默认路由(0.0.0.0/0)设置为 IGW(igw-e50b849d):
$ aws ec2 create-route --route-table-id rtb-a9e791d5 --gateway-id igw-e50b849d --destination-cidr-block 0.0.0.0/0
- 将路由表(
rtb-a9e791d5)关联到公共子网(subnet-6296863f):
$ aws ec2 associate-route-table --route-table-id rtb-a9e791d5 --subnet-id subnet-6296863f
- 在公共子网(
subnet-6296863f)上启用自动分配公共 IP:
$ aws ec2 modify-subnet-attribute --subnet-id subnet-6296863f --map-public-ip-on-launch
NAT-GW
如果子网的默认路由没有指向 IGW,会发生什么情况?该子网将被归类为私有子网,并且无法连接到互联网。然而,在某些情况下,您在私有子网中的虚拟机可能需要访问互联网。例如,下载一些安全补丁。
在这种情况下,您可以设置 NAT-GW。它允许您从私有子网访问互联网。然而,它仅允许出站流量,因此不能为私有子网分配公共 IP 地址。因此,它适用于后端实例,如数据库。
让我们创建 NAT-GW,并通过以下步骤将第二个子网(192.168.1.0/24)配置为指向 NAT-GW 的私有子网:
- NAT-GW 需要一个全球 IP 地址,因此创建弹性 IP(EIP):
$ aws ec2 allocate-address
{
"PublicIp": "18.232.18.38",
"AllocationId": "eipalloc-bad28bb3",
"Domain": "vpc"
}
- 在公共子网(
subnet-6296863f)上创建 NAT-GW 并分配 EIP(eipalloc-bad28bb3)。然后,捕获NatGatewayId。
由于 NAT-GW 需要访问互联网,它必须位于公共子网,而不是私有子网。
输入以下命令:
$ aws ec2 create-nat-gateway --subnet-id subnet-6296863f --allocation-id eipalloc-bad28bb3
{
"NatGateway": {
"CreateTime": "2018-04-14T18:49:36.000Z",
"NatGatewayAddresses": [
{
"AllocationId": "eipalloc-bad28bb3"
}
],
"NatGatewayId": "nat-0b12be42c575bba43",
"State": "pending",
"SubnetId": "subnet-6296863f",
"VpcId": "vpc-69cfbd12"
}
}
- 创建路由表并捕获
RouteTableId:
$ aws ec2 create-route-table --vpc-id vpc-69cfbd12
{
"RouteTable": {
"Associations": [],
"PropagatingVgws": [],
"RouteTableId": "rtb-70f1870c",
"Routes": [
{
"DestinationCidrBlock": "192.168.0.0/16",
"GatewayId": "local",
"Origin": "CreateRouteTable",
"State": "active"
}
],
"Tags": [],
"VpcId": "vpc-69cfbd12"
}
}
- 将路由表(
rtb-70f1870c)的默认路由(0.0.0.0/0)设置为 NAT-GW(nat-0b12be42c575bba43):
$ aws ec2 create-route --route-table-id rtb-70f1870c --nat-gateway-id nat-0b12be42c575bba43 --destination-cidr-block 0.0.0.0/0
- 将路由表(
rtb-70f1870c)关联到私有子网(subnet-ce947da9):
$ aws ec2 associate-route-table --route-table-id rtb-70f1870c --subnet-id subnet-ce947da9
安全组
在启动虚拟服务器(EC2)之前,您需要创建一个具有适当安全规则的安全组。现在,我们有两个子网,公共子网和私有子网。我们来设置公共子网,使其允许来自互联网的ssh(22/tcp)和http(80/tcp)。然后,设置私有子网,使其允许从公共子网进行 ssh 连接:
- 在 VPC(
vpc-69cfbd12)上为公共子网创建一个安全组:
$ aws ec2 create-security-group --vpc-id vpc-69cfbd12 --group-name public --description "public facing host"
{
"GroupId": "sg-dd8a3f94"
}
- 向公共安全组(
sg-dd8a3f94)添加允许 ssh 的规则:
$ aws ec2 authorize-security-group-ingress --group-id sg-dd8a3f94 --protocol tcp --port 22 --cidr 0.0.0.0/0
- 向公共安全组(
sg-dd8a3f94)添加允许http的规则:
$ aws ec2 authorize-security-group-ingress --group-id sg-dd8a3f94 --protocol tcp --port 80 --cidr 0.0.0.0/0
- 在 VPC(
vpc-69cfbd12)上为私有子网创建第二个安全组:
$ aws ec2 create-security-group --vpc-id vpc-69cfbd12 --group-name private --description "private subnet host"
{
"GroupId": "sg-a18c39e8"
}
- 向私有安全组 (
sg-a18c39e8) 添加一个ssh允许规则:
$ aws ec2 authorize-security-group-ingress --group-id sg-a18c39e8 --protocol tcp --port 22 --source-group sg-dd8a3f94
- 使用以下命令检查安全组列表:
$ aws ec2 describe-security-groups --filters "Name=vpc-id, Values=vpc-69cfbd12" --query "SecurityGroups[*].{id:GroupId,name:GroupName}" --output table
----------------------------
| DescribeSecurityGroups |
+--------------+-----------+
| id | name |
+--------------+-----------+
| sg-2ed56067 | default |
| sg-a18c39e8 | private |
| sg-dd8a3f94 | public |
+--------------+-----------+
EC2
现在你需要上传你的 ssh 公钥,然后启动公共子网和私有子网的 EC2 实例:
- 上传你的 ssh 公钥(假设你的公钥位于
~/.ssh/id_rsa.pub):
$ aws ec2 import-key-pair --key-name=chap6-key --public-key-material "`cat ~/.ssh/id_rsa.pub`"
-
启动第一个 EC2 实例,使用以下参数:
-
使用 Amazon Linux 镜像:
ami-1853ac65(Amazon Linux) -
T2.nano 实例类型:
t2.nano -
Ssh 密钥:
chap6-key -
公共子网:
subnet-6296863f -
公共安全组:
sg-dd8a3f94
-
$ aws ec2 run-instances --image-id ami-1853ac65 --instance-type t2.nano --key-name chap6-key --security-group-ids sg-dd8a3f94 --subnet-id subnet-6296863f
-
启动第二个 EC2 实例,使用以下参数:
-
使用 Amazon Linux 镜像:
ami-1853ac65 -
T2.nano 实例类型:
t2.nano -
Ssh 密钥:
chap6-key -
私有子网:
subnet-ce947da9 -
私有安全组:
sg-a18c39e8
-
$ aws ec2 run-instances --image-id ami-1853ac65 --instance-type t2.nano --key-name chap6-key --security-group-ids sg-a18c39e8 --subnet-id subnet-ce947da9
- 检查 EC2 实例的状态:
$ aws ec2 describe-instances --filters "Name=vpc-id,Values=vpc-69cfbd12" --query "Reservations[*].Instances[*].{id:InstanceId,PublicIP:PublicIpAddress,PrivateIP:PrivateIpAddress,Subnet:SubnetId}" --output=table
-------------------------------------------------------------------------------
| DescribeInstances |
+---------------+-----------------+------------------+------------------------+
| PrivateIP | PublicIP | Subnet | id |
+---------------+-----------------+------------------+------------------------+
| 192.168.0.206| 34.228.228.140| subnet-6296863f| i-03a0e49d26a2dafa4 |
| 192.168.1.218| None | subnet-ce947da9| i-063080766d2f2f520 |
+---------------+-----------------+------------------+------------------------+
- 从你的计算机通过 SSH(使用
-A选项转发你的认证信息)连接到公共 EC2 主机:
$ ssh -A ec2-user@34.228.228.140
The authenticity of host '34.228.228.140 (34.228.228.140)' can't be established.
ECDSA key fingerprint is SHA256:lE7hoBhHntVDvRItnasqyHRynajn2iuHJ7U3nsWySRU.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '34.228.228.140' (ECDSA) to the list of known hosts.
__| __|_ )
_| ( / Amazon Linux AMI
___|\___|___|
https://aws.amazon.com/amazon-linux-ami/2017.09-release-notes/
8 package(s) needed for security, out of 13 available
Run "sudo yum update" to apply all updates.
[ec2-user@ip-192-168-0-206 ~]$
- 安装并启动 nginx 到公共 EC2 主机:
[ec2-user@ip-192-168-0-206 ~]$ sudo yum -y install nginx
[ec2-user@ip-192-168-0-206 ~]$ sudo service nginx start
Starting nginx: [ OK ]
- 确保你能够从机器访问 nginx 服务器(见下图):

访问公共主机上的 nginx 网络服务器
- 从公共主机通过 SSH 连接到私有主机(必须使用私有 IP 地址):
$ ssh 192.168.1.218
- 确保私有主机能通过 NAT-GW 执行 yum 更新:
[ec2-user@ip-192-168-1-218 ~]$ sudo yum -y update
恭喜!你可以在 AWS 上设置自己的基础设施,如下图所示,包含以下内容:
-
一个 CIDR 为
192.168.0.0/16的 VPC -
IGW
-
NAT-GW
-
两个子网
-
公共子网:
192.168.0.0/24路由到 IGW -
私有子网:192.168.1.0/24 路由到 NAT-GW
-
-
两个 EC2 实例(公共和私有)
-
两个安全组(允许公共 http/ssh 和私有 ssh)
现在,来看一下下面的图示:

AWS 组件图
在这一部分,你已经学习了如何从零开始使用 AWS。我们涵盖了它的基本用法,但在设置 Kubernetes 到 AWS 时,了解这些内容非常重要。接下来,我们将探索如何在 AWS 上设置 Kubernetes。
使用 kops 设置 Kubernetes
什么是 kops?它是 Kubernetes 操作的缩写(github.com/kubernetes/kops)。类似于 kubeadm、minikube 和 kubespray,kops 减少了我们自己构建 Kubernetes 集群的繁重任务。它帮助创建集群,并为用户提供管理集群的接口。此外,kops 实现了更自动化的安装过程,并交付了生产级别的系统。它旨在支持主流云平台,如 AWS、GCE 和 VMware vSphere。在本教程中,我们将讨论如何使用 kops 运行 Kubernetes 集群。
准备就绪
在我们进行主要教程之前,需要在本地主机上安装 kops。这个步骤很简单,下载二进制文件并将其移动到执行文件的系统目录中:
// download the latest stable kops binary
$ curl -LO https://github.com/kubernetes/kops/releases/download/$(curl -s https://api.github.com/repos/kubernetes/kops/releases/latest | grep tag_name | cut -d '"' -f 4)/kops-linux-amd64
$ chmod +x kops-linux-amd64
$ sudo mv kops-linux-amd64 /usr/local/bin/kops
// verify the command is workable
$ kops version
Version 1.9.0 (git-cccd71e67)
接下来,我们需要在您的主机上准备一些 AWS 配置和集群所需的服务。请参考以下项目,并确保它们已经准备好:
-
IAM 用户:由于 kops 需要为您创建和构建多个 AWS 服务组件,您必须拥有一个具有 kops 所需权限的 IAM 用户。在前面的部分中,我们创建了一个名为 chap6 的 IAM 用户,并为其配置了以下具有 kops 所需权限的策略:
-
AmazonEC2FullAccess
-
AmazonRoute53FullAccess
-
AmazonS3FullAccess
-
IAMFullAccess
-
AmazonVPCFullAccess
-
然后,暴露 AWS 访问密钥 ID 和密钥作为环境变量,可以使该角色在执行kops命令时应用于主机:
$ export AWS_ACCESS_KEY_ID=${string of 20 capital character combination}
$ export AWS_SECRET_ACCESS_KEY=${string of 40 character and number combination}
-
为存储集群配置的 S3 桶做好准备:在接下来的演示中,S3 桶的名称将是
kubernetes-cookbook。 -
为集群访问点准备一个 Route53 DNS 域名:在接下来的演示中,我们使用的域名将是
k8s-cookbook.net。
如何操作...
我们可以通过包含完整配置的参数,轻松运行一个单一命令来启动 Kubernetes 集群。以下表格描述了这些参数:
| 参数 | 描述 | 示例中的值 |
|---|---|---|
--name |
这是集群的名称,也将是集群入口点的域名。因此,您可以使用自定义名称的 Route53 DNS 域名,例如{您的集群名称}.{您的 Route53 域名}。 |
my-cluster.k8s-cookbook.net |
--state |
这是指示存储集群状态的 S3 桶,格式为s3://{桶名称}。 |
s3://kubernetes-cookbook |
--zones |
这是您需要构建集群的可用区。 | us-east-1a |
--cloud |
这是云服务提供商。 | aws |
--network-cidr |
这里,kops 帮助创建一个新的 VPC 的独立 CIDR 范围。 | 10.0.0.0/16 |
--master-size |
这是 Kubernetes master 的实例大小。 | t2.large |
--node-size |
这是 Kubernetes 节点的实例大小。 | t2.medium |
--node-count |
这是集群中的节点数量。 | 2 |
--network |
这是在该集群中使用的覆盖网络。 | calico |
--topology |
这帮助您决定集群是否为公开的。 | private |
--ssh-public-key |
这帮助您为堡垒服务器分配 SSH 公钥,然后我们可以通过私钥登录。 | ~/.ssh/id_rsa.pub |
--bastion |
这告诉您创建堡垒服务器。 | N/A |
--yes |
这给出确认立即执行的权限。 | N/A |
现在我们准备好将配置组合成一个命令并执行它:
$ kops create cluster --name my-cluster.k8s-cookbook.net --state=s3://kubernetes-cookbook --zones us-east-1a --cloud aws --network-cidr 10.0.0.0/16 --master-size t2.large --node-size t2.medium --node-count 2 --networking calico --topology private --ssh-public-key ~/.ssh/id_rsa.pub --bastion --yes
...
I0408 15:19:21.794035 13144 executor.go:91] Tasks: 105 done / 105 total; 0 can run
I0408 15:19:21.794111 13144 dns.go:153] Pre-creating DNS records
I0408 15:19:22.420077 13144 update_cluster.go:248] Exporting kubecfg for cluster
kops has set your kubectl context to my-cluster.k8s-cookbook.net Cluster is starting. It should be ready in a few minutes.
...
几分钟后,命令会输出前面的日志,显示 AWS 服务已为您创建并提供服务,供您使用 kops 构建的 Kubernetes 集群。您甚至可以检查 AWS 控制台,以验证它们的关系,查看类似下面的图示:

由 kops 创建的 AWS 上 Kubernetes 集群的组件
它是如何工作的...
从本地用户可以通过 kops 命令与 AWS 上的集群进行交互:
//check the cluster
$ kops get cluster --state s3://kubernetes-cookbook
NAME CLOUD ZONES
my-cluster.k8s-cookbook.net aws us-east-1a
使用 kops 构建的 AWS 集群
此外,正如你在上一节中看到的,kops 集群创建的最后几条日志显示,客户端的环境也已经准备好。这意味着 kops 也帮助将 API 服务器安全地绑定到我们的主机。我们可以像在 Kubernetes 主节点上一样使用 kubectl 命令。我们需要做的就是手动安装 kubectl。安装过程与安装 kops 一样简单,只需下载二进制文件:
// install kubectl on local
$ curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
$ chmod +x kubectl
$ sudo mv kubectl /usr/local/bin/
// check the nodes in cluster on AWS
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
ip-10-0-39-216.ec2.internal Ready master 2m v1.8.7
ip-10-0-40-26.ec2.internal Ready node 31s v1.8.7
ip-10-0-50-147.ec2.internal Ready node 33s v1.8.7
然而,你仍然可以访问集群中的节点。由于集群部署在私有网络中,我们需要先登录到堡垒机服务器,然后跳转到节点进行下一步操作:
//add private key to ssh authentication agent
$ ssh-add ~/.ssh/id_rsa
//use your private key with flag “-i”
//we avoid it since the private key is in default location, ~/.ssh/id_rsa
//also use -A option to forward an authentication agent
$ ssh -A admin@bastion.my-cluster.k8s-cookbook.net
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sun Apr 8 19:37:31 2018 from 10.0.2.167
// access the master node with its private IP
admin@ip-10-0-0-70:~$ ssh 10.0.39.216
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sun Apr 8 19:36:22 2018 from 10.0.0.70
admin@ip-10-0-39-216:~$
删除 kops 构建的 AWS 集群
我们可以简单地使用 kops 命令删除集群,命令如下:
$ kops delete cluster --name my-cluster.k8s-cookbook.net --state s3://kubernetes-cookbook --yes
Deleted cluster: "my-cluster.k8s-cookbook.net"
它会为你清理 AWS 服务。但是一些你自己创建的服务,比如 S3 存储桶、具有强大授权的 IAM 角色,以及 Route53 域名;kops 不会删除它们。记得手动删除你不再使用的 AWS 服务。
另见
-
玩转亚马逊 Web 服务
-
将 AWS 作为 Kubernetes 云提供商
-
通过 kops 管理 AWS 上的 Kubernetes 集群
-
在第一章中,通过 kubeadm 在 Linux 上设置 Kubernetes 集群,构建你自己的 Kubernetes 集群
-
在第一章中,通过 kubespray 在 Linux 上设置 Kubernetes 集群,构建你自己的 Kubernetes 集群
将 AWS 作为 Kubernetes 云提供商
从 Kubernetes 1.6 开始,引入了 云控制器管理器 (CCM),它定义了一套接口,使得不同的云提供商能够在 Kubernetes 发布周期之外演进自己的实现。谈到云提供商,你不能忽视最大的玩家:亚马逊 Web 服务。根据云原生计算基金会的数据,在 2017 年,63% 的 Kubernetes 工作负载运行在 AWS 上。AWS CloudProvider 支持的服务有 弹性负载均衡器 (ELB) 和亚马逊 弹性块存储 (EBS) 作为 StorageClass。
在本书编写时,Amazon Elastic Container Service for Kubernetes(Amazon EKS)仍处于预览阶段,它是 AWS 中的托管 Kubernetes 服务。理想情况下,它将与 Kubernetes 更好地集成,例如应用负载均衡器(ALB)用于 Ingress、授权和网络。目前,在 AWS 中,VPC 中每个路由表的路由限制是 50;根据要求,最多可达到 100。然而,AWS 官方文档指出,如果路由超过 50,可能会影响网络性能。虽然 kops 默认使用 kubenet 网络,它为每个节点分配一个/24 CIDR,并在 AWS VPC 的路由表中配置路由。如果集群有超过 50 个节点,这可能会导致性能下降。使用 CNI 网络可能解决这个问题。
正在准备中
为了跟随本食谱中的示例,您需要在 AWS 中创建一个 Kubernetes 集群。以下示例使用 kops 在 AWS 中提供一个名为k8s-cookbook.net的 Kubernetes 集群;如前面所示的食谱所示,设置$KOPS_STATE_STORE为一个 s3 桶来存储您的 kops 配置和元数据:
# kops create cluster --master-count 1 --node-count 2 --zones us-east-1a,us-east-1b,us-east-1c --node-size t2.micro --master-size t2.small --topology private --networking calico --authorization=rbac --cloud-labels "Environment=dev" --state $KOPS_STATE_STORE --name k8s-cookbook.net
I0408 16:10:12.212571 34744 create_cluster.go:1318] Using SSH public key: /Users/k8s/.ssh/id_rsa.pub I0408 16:10:13.959274 34744 create_cluster.go:472] Inferred --cloud=aws from zone "us-east-1a"
I0408 16:10:14.418739 34744 subnets.go:184] Assigned CIDR 172.20.32.0/19 to subnet us-east-1a
I0408 16:10:14.418769 34744 subnets.go:184] Assigned CIDR 172.20.64.0/19 to subnet us-east-1b I0408 16:10:14.418777 34744 subnets.go:184] Assigned CIDR 172.20.96.0/19 to subnet us-east-1c
I0408 16:10:14.418785 34744 subnets.go:198] Assigned CIDR 172.20.0.0/22 to subnet utility-us-east-1a I0408 16:10:14.418793 34744 subnets.go:198] Assigned CIDR 172.20.4.0/22 to subnet utility-us-east-1b
I0408 16:10:14.418801 34744 subnets.go:198] Assigned CIDR 172.20.8.0/22 to subnet utility-us-east-1c ...
Finally configure your cluster with: kops update cluster k8s-cookbook.net --yes
一旦我们运行推荐的kops update cluster <cluster_name> --yes命令,几分钟后,集群就会启动并运行。我们可以使用kops validate cluster来检查集群组件是否已全部启动:
# kops validate cluster
Using cluster from kubectl context: k8s-cookbook.net
Validating cluster k8s-cookbook.net
INSTANCE GROUPS
NAME ROLE MACHINETYPE MIN MAX SUBNETS
master-us-east-1a Master t2.small 1 1 us-east-1a
nodes Node t2.micro 2 2 us-east-1a,us-east-1b,us-east-1c
NODE STATUS
NAME ROLE READY
ip-172-20-44-140.ec2.internal node True
ip-172-20-62-204.ec2.internal master True
ip-172-20-87-38.ec2.internal node True
Your cluster k8s-cookbook.net is ready
我们可以开始了!
如何操作...
在 AWS 上运行 Kubernetes 时,我们可以使用两种可能的集成方式:作为LoadBalancer类型的服务的 ELB,以及作为StorageClass的 Amazon Elastic Block Store。
作为 LoadBalancer 服务的弹性负载均衡器
让我们创建一个LoadBalancer服务,并在其下创建 Pod,这是我们在第三章中学到的,与容器玩耍:
# cat aws-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 3
selector:
matchLabels:
run: nginx
template:
metadata:
labels:
run: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
ports:
- port: 80
targetPort: 80
type: LoadBalancer
selector:
run: nginx
在前面的模板中,我们声明了一个 nginx Pod,并将其与LoadBalancer服务关联。该服务将数据包定向到容器端口80:
# kubectl create -f aws-service.yaml
deployment.apps "nginx" created
service "nginx" created
让我们描述一下我们的nginx服务:
# kubectl describe svc nginx
Name: nginx
Namespace: default
Labels: <none>
Annotations: <none>
Selector: run=nginx
Type: LoadBalancer
IP: 100.68.35.30
LoadBalancer Ingress: a9da4ef1d402211e8b1240ef0c7f25d3-1251329976.us-east-1.elb.amazonaws.com
Port: <unset> 80/TCP
TargetPort: 80/TCP
NodePort: <unset> 31384/TCP
Endpoints: 100.124.40.196:80,100.99.102.130:80,100.99.102.131:80
Session Affinity: None
External Traffic Policy: Cluster
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal EnsuringLoadBalancer 2m service-controller Ensuring load balancer
Normal EnsuredLoadBalancer 2m service-controller Ensured load balancer
服务创建后,我们会发现 AWS CloudProvider 将提供一个经典负载均衡器,端点为adb576a05401911e8b1240ef0c7f25d3-1637943008.us-east-1.elb.amazonaws.com。我们可以通过 aws 命令行界面检查其详细设置(aws.amazon.com/cli/)。
要安装 AWS CLI,您可以在 Mac 或 Linux 上使用 pip 安装(pip install awscli);对于 Windows 用户,您需要从官方网站下载安装程序。
AWS CLI 命令的组合是aws [options] <command> <subcommand> [<subcommand> ...] [parameters]。要列出负载均衡器,我们将使用aws elb describe-load-balancers作为主要命令。使用--load-balancer-names参数将按名称筛选负载均衡器,对于--output参数,您可以选择文本、JSON 或表格格式:
# aws elb describe-load-balancers --load-balancer-names a9da4ef1d402211e8b1240ef0c7f25d3 --output text
LOADBALANCERDESCRIPTIONS a9da4ef1d402211e8b1240ef0c7f25d3-1251329976.us-east-1.elb.amazonaws.com Z35SXDOTRQ7X7K 2018-04-14T20:30:45.990Z a9da4ef1d402211e8b1240ef0c7f25d3-1251329976.us-east-1.elb.amazonaws.com a9da4ef1d402211e8b1240ef0c7f25d3 internet-facing vpc-07374a7c
AVAILABILITYZONES us-east-1a
AVAILABILITYZONES us-east-1b
AVAILABILITYZONES us-east-1c
HEALTHCHECK 2 10 TCP:31384 5 6
INSTANCES i-03cafedc27dca591b
INSTANCES i-060f9d17d9b473074
LISTENER 31384 TCP 80 TCP
SECURITYGROUPS sg-3b4efb72
SOURCESECURITYGROUP k8s-elb-a9da4ef1d402211e8b1240ef0c7f25d3 516726565417
SUBNETS subnet-088f9d27
SUBNETS subnet-e7ec0580
SUBNETS subnet-f38191ae
如果我们访问此 ELB 端点端口80,我们将看到 nginx 欢迎页面:

访问 ELB 端点以访问 LoadBalancer Service
在后台,AWS CloudProvider 创建了一个 AWS 弹性负载均衡器,并通过我们刚刚定义的 Service 配置了其入口规则和监听器。以下是流量进入 Pods 的示意图:

Kubernetes 资源与 AWS 资源对于 LoadBalancer 类型 Service 的示意图
外部负载均衡器接收请求并使用轮询算法将其转发到 EC2 实例。对于 Kubernetes,流量通过 NodePort 进入 Service,启动 Service 到 Pod 的通信。关于外部到 Service 和 Service 到 Pod 的通信,您可以参考第三章,与容器玩耍。
弹性块存储作为 StorageClass
我们在第二章,Kubernetes 概念解析 中了解了 Volumes。我们知道 PersistentVolumeClaims 用于将存储资源从用户中抽象出来。它可以通过 StorageClass 动态提供 PersistentVolume。StorageClass 中的默认提供者在 AWS CloudProvider 中是弹性块存储服务 (aws-ebs)。每当你请求一个 PVC 时,aws-ebs 提供者会在 AWS EBS 中创建一个卷。
让我们检查一下我们集群中的存储类:
// list all storageclass
# kubectl get storageclass
NAME PROVISIONER AGE
default kubernetes.io/aws-ebs 2h
gp2 (default) kubernetes.io/aws-ebs 2h
In this recipe, we'll reuse the PVC example we mentioned in Chapter 2-6:
# cat chapter2/2-6_volumes/2-6-7_pvc.yaml
apiVersion: "v1"
kind: "PersistentVolumeClaim"
metadata:
name: "pvclaim01"
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
// create pvc
# kubectl create -f chapter2/2-6_volumes/2-6-7_pvc.yaml
persistentvolumeclaim "pvclaim01" created
// check pvc is created successfully.
# kubectl get pvc
NAME STATUS VOLUME CAPACITY
pvclaim01 Bound pvc-e3d881d4-402e-11e8-b124-0ef0c7f25d36 1Gi
ACCESS MODES STORAGECLASS AGE
RWO gp2 16m
创建 PVC 后,将创建一个关联的 PV:
# kubectl get pv
NAME CAPACITY ACCESS MODES
pvc-e3d881d4-402e-11e8-b124-0ef0c7f25d36 1Gi RWO
RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
Delete Bound default/pvclaim01 gp2 16m
你可以在这里更仔细地查看 PV:
# kubectl describe pv pvc-e3d881d4-402e-11e8-b124-0ef0c7f25d36
Name: pvc-e3d881d4-402e-11e8-b124-0ef0c7f25d36
Labels: failure-domain.beta.kubernetes.io/region=us-east-1
failure-domain.beta.kubernetes.io/zone=us-east-1a
Annotations: kubernetes.io/createdby=aws-ebs-dynamic-provisioner
pv.kubernetes.io/bound-by-controller=yes
pv.kubernetes.io/provisioned-by=kubernetes.io/aws-ebs
Claim: default/pvclaim01
...
Source:
Type: AWSElasticBlockStore (a Persistent Disk resource in AWS)
VolumeID: aws://us-east-1a/vol-035ca31b9cc1820d7
FSType: ext4
Partition: 0
ReadOnly: false
我们可以发现它与我们刚刚创建的 pvclaim01 相关联,并且源类型是 AWSElasticBlockStore,正如预期的那样。
我们可以使用 AWS CLI 检查我们在 EBS 中创建的卷。使用 --filter Name=tag-value 我们可以筛选 EBS 中的卷:
// aws ec2 describe-volumes --filter Name=tag-value,Values=$PV_NAME
# aws ec2 describe-volumes --filter Name=tag-value,Values="pvc-e3d881d4-402e-11e8-b124-0ef0c7f25d36"{
"Volumes": [
{
"AvailabilityZone": "us-east-1a",
"Tags": [
{ "Value": "k8s-cookbook.net",
"Key": "KubernetesCluster" },
{ "Value": "default",
"Key": "kubernetes.io/created-for/pvc/namespace" },
{ "Value": "k8s-cookbook.net-dynamic-pvc-e3d881d4-402e-11e8-b124-0ef0c7f25d36",
"Key": "Name" },
{ "Value": "pvclaim01",
"Key": "kubernetes.io/created-for/pvc/name" },
{ "Value": "owned",
"Key": "kubernetes.io/cluster/k8s-cookbook.net" },
{ "Value": "pvc-e3d881d4-402e-11e8-b124-0ef0c7f25d36",
"Key": "kubernetes.io/created-for/pv/name" }],
"VolumeType": "gp2",
"VolumeId": "vol-035ca31b9cc1820d7",
...
}
]
}
我们可以看到 EBS 资源已经被标记了许多不同的值:通过观察这些标签,我们可以知道这个 EBS 卷与哪个 Kubernetes 集群、命名空间、PVC 和 PV 相关联。
由于 StorageClass 和 CloudProvider 支持动态提供,卷管理不再是一个巨大痛点。我们可以即时创建和销毁 PV。
还有更多…
在编写本书时,Kubernetes 1.10 尚未提供支持 AWS CloudProvider 中 Ingress 集成的原生方式(理想情况下是通过应用负载均衡器)。作为替代方案,kops 提供了可以实现这一功能的附加组件。第一个是 ingress-nginx (github.com/kubernetes/kops/tree/master/addons/ingress-nginx),它由 nginx (nginx.org) 和 AWS 弹性负载均衡器支持。请求会通过 ELB 到达 nginx,nginx 会根据 Ingress 中的路径定义分发请求。另一个替代方案是运行 skipper 作为 kubernetes-ingress-controller (zalando.github.io/skipper/dataclients/kubernetes)。kops 还提供了附加组件,帮助您部署并利用 skipper 和 AWS 应用负载均衡器 (github.com/kubernetes/kops/tree/master/addons/kube-ingress-aws-controller)。
我们预计 CCM 和 Amazon EKS (aws.amazon.com/eks/) 将通过 AWS 应用负载均衡器提供更多原生的 Ingress 集成,未来还会有更多更新!
使用 kops 管理 AWS 上的 Kubernetes 集群
在 kops 中,Kubernetes 主节点和节点都作为 AWS 中的自动扩展组运行。在 kops 中,这个概念被称为 实例组 (ig),表示集群中相同类型的实例。类似于跨可用区的节点,或每个可用区的主节点,我们可以通过 kops 命令行检查:
// kops get instancegroups or kops get ig
# kops get instancegroups --name k8s-cookbook.net
NAME ROLE MACHINETYPE MIN MAX ZONES
master-us-east-1a Master t2.small 1 1 us-east-1a
nodes Node t2.micro 2 2 us-east-1a,us-east-1b,us-east-1c
使用 kops,您可以更改实例类型,调整实例组(主节点和节点)大小,进行滚动更新,升级集群。kops 还支持针对特定 AWS 功能的配置,例如为集群中的实例启用 AWS 详细监控。
准备工作
执行本配方时,您需要在 AWS 上通过 kops 部署的 Kubernetes 集群。您需要按照本章前面的配方来启动一个集群。这里,我们将使用之前配方中创建的相同集群:
# kops validate cluster
Using cluster from kubectl context: k8s-cookbook.net
Validating cluster k8s-cookbook.net
INSTANCE GROUPS
NAME ROLE MACHINETYPE MIN MAX SUBNETS
master-us-east-1a Master t2.small 1 1 us-east-1a
nodes Node t2.micro 2 2 us-east-1a,us-east-1b,us-east-1c
NODE STATUS
NAME ROLE READY
ip-172-20-44-140.ec2.internal node True
ip-172-20-62-204.ec2.internal master True
ip-172-20-87-38.ec2.internal node True
Your cluster k8s-cookbook.net is ready
在之前的配方中,我们将 KOPS_STATE_STORE 环境变量设置为我们的 S3 存储桶名称之一,格式为 s3://<bucket_name>,用于存储 kops 配置和元数据。
操作步骤...
接下来的子节将介绍集群管理员可能会遇到的一些常见操作示例。
修改和调整实例组
如果您手动部署所有实例,修改实例组可能会很麻烦。您需要逐一更新实例或重新启动它们。而使用 kops,我们可以轻松地执行更新而不感到困难。
更新节点
使用 kops edit 命令,我们可以修改实例类型和节点数量:
// kops edit ig nodes
# kops edit instancegroups nodes --name k8s-cookbook.net
apiVersion: kops/v1alpha2
kind: InstanceGroup
metadata:
creationTimestamp: 2018-04-14T19:06:47Z
labels:
kops.k8s.io/cluster: k8s-cookbook.net
name: nodes
spec:
image: kope.io/k8s-1.8-debian-jessie-amd64-hvm-ebs-2018-02-08
machineType: t2.micro
maxSize: 2
minSize: 2
nodeLabels:
kops.k8s.io/instancegroup: nodes
role: Node
subnets:
- us-east-1a
- us-east-1b
- us-east-1c
在本示例中,我们将 minSize 和 maxSize 从 2 修改为 3。修改后,我们需要运行 kops update 来使其生效:
# kops update cluster k8s-cookbook.net --yes
...
I0414 21:23:52.505171 16291 update_cluster.go:291] Exporting kubecfg for cluster
kops has set your kubectl context to k8s-cookbook.net
Cluster changes have been applied to the cloud.
Changes may require instances to restart: kops rolling-update cluster
某些更新需要滚动更新集群。在这个示例中,kops 已经更新了 AWS 自动扩展组中的配置。AWS 将启动一个新实例以适应这个变化。以下是来自 AWS 自动扩展组控制台的截图:

nodes_in_AWS_Auto_Scaling_Groups
我们可以看到配置已更新,AWS 正在扩展新实例。几分钟后,我们可以通过 kops validate 或 kubectl get nodes 检查集群状态:
# kops validate cluster
Using cluster from kubectl context: k8s-cookbook.net
Validating cluster k8s-cookbook.net
INSTANCE GROUPS
NAME ROLE MACHINETYPE MIN MAX SUBNETS
master-us-east-1a Master t2.small 1 1 us-east-1a
nodes Node t2.micro 3 3 us-east-1a,us-east-1b,us-east-1c
NODE STATUS
NAME ROLE READY
ip-172-20-119-170.ec2.internal node True
ip-172-20-44-140.ec2.internal node True
ip-172-20-62-204.ec2.internal master True
ip-172-20-87-38.ec2.internal node True
一切看起来都很好!
更新主节点
更新主节点与更新节点相同。请注意,同一可用区中的主节点在同一实例组内。这意味着你无法向主节点实例组中添加额外的子网。在以下示例中,我们将把主节点数量从 1 调整为 2。
在这个配方中,我们只将主节点数量设为 1。在实际应用中,推荐的做法是将主节点部署到至少两个可用区,每个可用区配置三个主节点(一个 kops 实例组)。你可以通过启动集群时使用 --master-count 和 --master-zones 参数来实现。
现在请看以下命令:
# kops edit ig master-us-east-1a
apiVersion: kops/v1alpha2
kind: InstanceGroup
metadata:
creationTimestamp: 2018-04-14T19:06:47Z
labels:
kops.k8s.io/cluster: k8s-cookbook.net
name: master-us-east-1a
spec:
image: kope.io/k8s-1.8-debian-jessie-amd64-hvm-ebs-2018-02-08
machineType: t2.small
maxSize: 1
minSize: 1
nodeLabels:
kops.k8s.io/instancegroup: master-us-east-1a
role: Master
subnets:
- us-east-1a
在应用更改之前,我们可以在 dry run 模式下运行更新集群命令,不带 --yes 参数:
# kops update cluster k8s-cookbook.net
...
Will modify resources:
AutoscalingGroup/master-us-east-1a.masters.k8s-cookbook.net
MinSize 1 -> 2
MaxSize 1 -> 2
Must specify --yes to apply changes
在验证 dry run 消息符合预期后,我们可以按以下方式执行更新。在这种情况下,我们必须进行滚动更新。
如何判断是否需要滚动更新
如果我们在之前的示例中没有运行 kops 滚动更新,当运行 kops validate cluster 时,kops 会显示验证错误:
验证错误
KIND NAME MESSAGE
InstanceGroup master-us-east-1a 实例组 master-us-east-1a 的节点不足,1 对 2
记得将 k8s-cookbook.net 替换为你的集群名称。
# kops update cluster k8s-cookbook.net –-yes && kops rolling-update cluster
...
Using cluster from kubectl context: k8s-cookbook.net
NAME STATUS NEEDUPDATE READY MIN MAX NODES
master-us-east-1a Ready 0 2 2 2 1
nodes Ready 0 3 3 3 3
No rolling-update required.
就像修改节点一样,我们可以使用 kubectl get nodes 和 kops validate cluster 来检查新主节点是否已加入集群。
升级集群
为了演示如何升级 Kubernetes 版本,我们将首先使用 1.8.7 版本启动集群。有关参数的详细说明,请参考本章前面的配方。请输入以下命令:
// launch a cluster with additional parameter --kubernetes-version 1.8.7 # kops create cluster --master-count 1 --node-count 2 --zones us-east-1a,us-east-1b,us-east-1c --node-size t2.micro --master-size t2.small --topology private --networking calico --authorization=rbac --cloud-labels "Environment=dev" --state $KOPS_STATE_STORE --kubernetes-version 1.8.7 --name k8s-cookbook.net --yes
几分钟后,我们可以看到主节点和节点都已经升级到版本 1.8.7:
# kubectl get nodes
NAME STATUS ROLES AGE VERSION
ip-172-20-44-128.ec2.internal Ready master 3m v1.8.7
ip-172-20-55-191.ec2.internal Ready node 1m v1.8.7
ip-172-20-64-30.ec2.internal Ready node 1m v1.8.7
在以下示例中,我们将演示如何通过 kops 将 Kubernetes 集群从 1.8.7 升级到 1.9.3。首先,运行 kops upgrade cluster 命令,kops 将显示我们可以升级到的最新版本:
# kops upgrade cluster k8s-cookbook.net --yes
ITEM PROPERTY OLD NEW
Cluster KubernetesVersion 1.8.7 1.9.3
Updates applied to configuration. You can now apply these changes,
using `kops update cluster k8s-cookbook.net`
这表明配置已经更新,我们现在需要更新集群。我们先运行 dryrun 模式的命令,检查将要修改的内容:
// update cluster
# kops update cluster k8s-cookbook.net
...
Will modify resources:
LaunchConfiguration/master-us-east-1a.masters.k8s-cookbook.net
UserData
...
+ image: gcr.io/google_containers/kube-apiserver:v1.9.3
- image: gcr.io/google_containers/kube-apiserver:v1.8.7
...
+ image: gcr.io/google_containers/kube-controller
manager:v1.9.3
- image: gcr.io/google_containers/kube-controller-manager:v1.8.7
...
hostnameOverride: '@aws'
+ image: gcr.io/google_containers/kube-proxy:v1.9.3
- image: gcr.io/google_containers/kube-proxy:v1.8.7
logLevel: 2
kubeScheduler:
+ image: gcr.io/google_containers/kube-scheduler:v1.9.3
- image: gcr.io/google_containers/kube
scheduler:v1.8.7
...
Must specify --yes to apply changes
我们可以看到所有组件已从 v1.8.7 升级到 v1.9.3,并出现在自动扩展启动配置中。验证一切正常后,我们可以运行相同的命令,并添加 --yes 参数:
// run the same command with --yes
# kops update cluster k8s-cookbook.net --yes
...
kops has set your kubectl context to k8s-cookbook.net
Cluster changes have been applied to the cloud.
Changes may require instances to restart: kops rolling-update cluster
在这种情况下,我们需要对集群执行滚动更新:
# kops rolling-update cluster --yes
Using cluster from kubectl context: k8s-cookbook.net
NAME STATUS NEEDUPDATE READY MIN MAX NODES
master-us-east-1a NeedsUpdate 1 0 1 1 1
nodes NeedsUpdate 2 0 2 2 2
I0414 22:45:05.887024 51333 rollingupdate.go:193] Rolling update completed for cluster "k8s-cookbook.net"!
所有节点已升级到 1.9.3!在执行滚动更新时,kops 会首先清空一个实例,然后将节点标记为不可调度(cordon)。自动扩展组将启动另一个带有更新用户数据的节点,该数据包含包含更新的 Kubernetes 组件镜像。为了避免停机,您应该将多个主节点和节点作为基本部署。
滚动更新完成后,我们可以通过kubectl get nodes检查集群版本:
# kubectl get nodes
NAME STATUS ROLES AGE VERSION
ip-172-20-116-81.ec2.internal Ready node 14m v1.9.3
ip-172-20-41-113.ec2.internal Ready master 17m v1.9.3
ip-172-20-56-230.ec2.internal Ready node 8m v1.9.3
所有节点已升级到 1.9.3!
还有更多内容...
在 kops 中,有许多有用的插件,如自动扩展节点(github.com/kubernetes/kops/tree/master/addons/cluster-autoscaler)和将服务映射到 Route53 中的记录(github.com/kubernetes/kops/tree/master/addons/route53-mapper)。请参考插件页面以了解更多信息!
另请参见
-
第二章中的部署 API,走进 Kubernetes 概念
-
第四章中的构建多个主节点,构建高可用性集群
-
第七章中的管理 GKE 上的 Kubernetes 集群,在 GCP 上构建 Kubernetes
第七章:在 GCP 上构建 Kubernetes
在本章中,我们将在以下配方中使用Google Cloud Platform(GCP):
-
玩转 GCP
-
通过Google Kubernetes Engine(GKE)设置托管的 Kubernetes
-
在 GKE 上探索 Kubernetes CloudProvider
-
在 GKE 上管理 Kubernetes 集群
玩转 GCP
GCP 在公有云行业越来越受欢迎。它有一些类似于 AWS 的概念,比如 VPC、计算引擎、持久磁盘、负载均衡和多个托管服务。最有趣的服务是 GKE,这是一个托管的 Kubernetes 集群。我们将探索如何使用 GCP 和 GKE。
准备工作
要使用 GCP,你需要有一个 Google 账户,如 Gmail(mail.google.com/mail/),很多人已经拥有。然后,按照以下步骤使用你的 Google 账户注册 GCP:
-
访问
cloud.google.com网站,然后点击“免费试用”按钮 -
使用你的 Google 账户登录 Google
-
在 GCP 注册并输入你的个人信息和账单信息
就这样!
注册完成后,你会看到 GCP Web 控制台页面。刚开始时,它可能会要求你创建一个项目;默认名称可能是“我的第一个项目”。你可以保留这个名称,但我们将在本章中创建另一个项目,更好地帮助你理解。
GCP Web 控制台作为第一步已经足够。但不推荐 DevOps 持续使用 Web 控制台,因为人工输入总是会引发人为错误,而且 Google 未来可能会更改 Web 控制台的设计。
因此,我们将使用 CLI。GCP 提供了一款名为 Cloud SDK 的 CLI 工具(cloud.google.com/sdk/)。所以,让我们创建一个新的 GCP 项目,并在你的机器上安装 Cloud SDK。
创建一个 GCP 项目
我们将按照以下步骤从零开始创建一个新项目。这将帮助你理解 GCP 项目是如何工作的:
- 点击“我的第一个项目”链接,进入项目页面:

导航到项目链接
- 你可能会看到自己的项目供你选择,但这次点击+按钮创建一个新项目:

创建一个新项目
- 输入项目名称为
Kubernetes Cookbook。然后,GCP 会生成并分配一个项目 ID,例如 kubernetes-cookbook-12345。请记住这个项目 ID。
你可能会注意到,你的项目 ID 并不是 kubernetes-cookbook,就像下面截图中显示的 kubernetes-cookbook-194302 一样。即使你点击编辑尝试更改为 kubernetes-cookbook,也不允许更改,因为项目 ID 是所有 GCP 用户唯一的字符串。我们已经使用了 kubernetes-cookbook 这个项目 ID。

项目名称和项目 ID
- 几分钟后,你的项目就准备好了。返回顶部横幅的项目选择页面,然后选择你的 Kubernetes Cookbook 项目:

选择 Kubernetes Cookbook 项目
完成!你可以随时切换到你的项目和 Kubernetes Cookbook 项目。这是一个隔离的环境;任何 VPC、VM、IAM 用户,甚至计费方式都是独立的。
安装 Cloud SDK
接下来,在你的机器上安装 Cloud SDK。它支持 Windows、Mac 和 Linux 平台。所有这些平台都需要 Python 解释器版本 2.7,但大多数 macOS 和 Linux 安装使用默认值。
另一方面,Windows 默认没有 Python 解释器。然而,在 Windows 的 Cloud SDK 安装程序中,可以安装 Python。让我们一步步在 Windows 和 macOS 上安装 Cloud SDK。
在 Windows 上安装 Cloud SDK
Cloud SDK 提供了适用于 Windows 的安装程序。它还包括 Windows 上的 Python 解释器。请按照以下步骤在你的 Windows 机器上安装:
-
在 Windows 上下载 Cloud SDK 安装程序(
dl.google.com/dl/cloudsdk/channels/rapid/GoogleCloudSDKInstaller.exe)。 -
运行 Cloud SDK 安装程序。
如果你从未在 Windows 机器上安装过 Python 解释器,你需要选择 Bundled Python 选项:

Windows 的 Cloud SDK 安装程序
-
除此之外,继续使用默认选项完成安装。
-
安装完成后,你可以在 Google Cloud SDK 程序组中找到 Google Cloud SDK Shell。点击它启动 Google Cloud SDK Shell:

Google Cloud SDK 程序组中的 Google Cloud SDK Shell
- 输入
gcloud info来检查是否能看到 Cloud SDK 版本:

在 Windows 上运行 gcloud 命令
在 Linux 和 macOS 上安装 Cloud SDK
在 Linux 和 macOS 上安装 Cloud SDK 遵循此处列出的步骤。让我们在你的主目录下安装 Cloud SDK:
-
打开终端。
-
输入以下命令以下载并运行 Cloud SDK 安装程序:
$ curl https://sdk.cloud.google.com | bash
- 它会询问你希望的安装目录。默认情况下,它会安装在你的主目录下。所以,输入
return:
Installation directory (this will create a google-cloud-sdk subdirectory) (/Users/saito):
- 它会询问是否发送用户使用数据;当发生崩溃时,它会发送一些信息。根据你的隐私政策,如果你不希望将任何数据发送给 Google,选择
n。否则选择Y来改善其质量:
Do you want to help improve the Google Cloud SDK (Y/n)? n
- 它会询问是否通过添加
gcloud命令到你的命令搜索路径中来更新.bash_profile;输入y继续:
Modify profile to update your $PATH and enable shell command
completion?
Do you want to continue (Y/n)? y
The Google Cloud SDK installer will now prompt you to update an rc
file to bring the Google Cloud CLIs into your environment.
Enter a path to an rc file to update, or leave blank to use
[/Users/saito/.bash_profile]:
- 打开另一个终端,或者输入
exec -l $SHELL来刷新你的命令搜索路径:
//reload .bash_profile
$ exec -l $SHELL
//check gcloud command is in your search path
$ which gcloud
/Users/saito/google-cloud-sdk/bin/gcloud
- 输入
gcloud info来检查是否能看到 Cloud SDK 版本:
$ gcloud info
Google Cloud SDK [187.0.0]
Platform: [Mac OS X, x86_64] ('Darwin', 'Hideto-Saito-no-MacBook.local', '17.4.0', 'Darwin Kernel Version 17.4.0: Sun Dec 17 09:19:54 PST 2017; root:xnu-4570.41.2~1/RELEASE_X86_64', 'x86_64', 'i386')
Python Version: [2.7.14 (default, Jan 21 2018, 12:22:04) [GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.38)]]
Python Location: [/usr/local/Cellar/python/2.7.14_2/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python]
现在,你可以开始配置 Cloud SDK 了!
配置 Cloud SDK
你可以通过以下步骤配置 Windows 和 Linux/macOS 版 Cloud SDK:
-
启动 Google Cloud SDK Shell(Windows)或打开终端(Linux/macOS)。
-
输入
gcloud init;它会要求你登录到 Google 账户。输入y并按回车:
You must log in to continue. Would you like to log in (Y/n)? y
-
它将打开一个浏览器,导航到 Google 登录页面;请使用你的 Google 账户登录 GCP 账户。
-
它会询问 Cloud SDK 是否可以访问你的 Google 账户信息。点击
ALLOW按钮。 -
回到终端——它会询问你想使用哪个项目。我们选择你创建的 Kubernetes Cookbook 项目:
Pick cloud project to use:
[1] my-first-project-194302
[2] kubernetes-cookbook
[3] Create a new project
Please enter numeric choice or text value (must exactly match list item): 2
- 它会询问你是否配置
Compute Engine。这次我们输入n跳过此步骤:
Do you want to configure Google Compute Engine
(https://cloud.google.com/compute) settings (Y/n)? n
现在你可以开始使用 Cloud SDK 控制 GCP 了。我们将创建 VPC、子网和防火墙规则,然后启动一个 VM 实例来设置我们的 GCP 基础设施。
如果你选择了错误的项目,或者想重新尝试,随时可以通过gcloud init命令重新配置你的设置。
如何操作...
我们将通过 GCP 的基本功能来设置 Kubernetes Cookbook 项目下的基础设施。通过使用gcloud命令,我们将创建以下组件:
-
一个新的 VPC
-
在 VPC 中有两个子网(
us-central1和us-east1) -
三个防火墙规则(
public-ssh,public-http,和private-ssh) -
我们将把你的 ssh 公钥添加到项目范围的元数据中
总体而言,你的基础设施将类似于以下内容。让我们逐一配置这些组件:

目标基础设施
创建 VPC
GCP 中的 VPC 与 AWS 类似,但无需绑定特定区域,也无需设置 CIDR 地址范围。这意味着你可以创建一个覆盖所有区域的 VPC。默认情况下,你的 Kubernetes Cookbook 项目有一个默认的 VPC。
然而,为了更好地理解,我们将按照以下步骤创建一个新的 VPC:
- 运行
gcloud compute networks命令创建一个新的 VPC。名称为chap7,子网模式为custom,这意味着子网不会自动创建。所以下一步我们将手动添加子网:
$ gcloud compute networks create chap7 --subnet-mode=custom
- 检查 VPC 列表;你应该有两个 VPC,
defaultVPC 和chap7VPC:
$ gcloud compute networks list
NAME SUBNET_MODE BGP_ROUTING_MODE IPV4_RANGE GATEWAY_IPV4
chap7 CUSTOM REGIONAL
default AUTO REGIONAL
创建子网
我们将按照以下步骤,在chap7 VPC(网络)下创建两个子网:
- 为了创建一个子网,你必须选择区域。通过输入
gcloud compute regions list,你可以查看哪些区域对你可用:

显示 GCP 区域列表
- 我们选择
us-central1和us-east1,在chap7VPC 下创建两个子网,配置如下:
| 子网名称 | VPC | CIDR 范围 | 区域 |
|---|---|---|---|
chap7-us-central1 |
chap7 |
192.168.1.0/24 |
us-central1 |
chap7-us-east1 |
chap7 |
192.168.2.0/24 |
us-east1 |
$ gcloud compute networks subnets create chap7-us-central1 --network=chap7 --range=192.168.1.0/24 --region us-central1
$ gcloud compute networks subnets create chap7-us-east1 --network=chap7 --range=192.168.2.0/24 --region us-east1
- 查看以下命令,以检查子网是否配置正确:
$ gcloud compute networks subnets list --network=chap7
NAME REGION NETWORK RANGE
chap7-us-east1 us-east1 chap7 192.168.2.0/24
chap7-us-central1 us-central1 chap7 192.168.1.0/24
创建防火墙规则
防火墙规则类似于 AWS 的安全组,你可以定义入站和出站数据包过滤器。它们使用网络标签,作为标签,用来区分防火墙规则和 VM 实例。因此,VM 实例可以指定零个或多个网络标签,防火墙规则将应用于具有相同网络标签的 VM 实例。
因此,我们需要在创建防火墙规则时设置目标网络标签。总体来说,我们将创建三条防火墙规则,具有以下配置:
| 防火墙规则名称 | 目标 VPC | 允许端口 | 允许来自 | 目标网络标签 |
|---|---|---|---|---|
public-ssh |
chap7 |
ssh (22/tcp) |
All (0.0.0.0/0) |
public |
public-http |
chap7 |
http (80/tcp) |
All (0.0.0.0/0) |
public |
private-ssh |
chap7 |
ssh (22/tcp) |
Host which has a public network tag | private |
- 创建一个
public-ssh规则:
$ gcloud compute firewall-rules create public-ssh --network=chap7 --allow="tcp:22" --source-ranges="0.0.0.0/0" --target-tags="public"
- 创建一个
public-http规则:
$ gcloud compute firewall-rules create public-http --network=chap7 --allow="tcp:80" --source-ranges="0.0.0.0/0" --target-tags="public"
- 创建一个
private-ssh规则:
$ gcloud compute firewall-rules create private-ssh --network=chap7 --allow="tcp:22" --source-tags="public" --target-tags="private"
- 检查所有防火墙规则:
$ gcloud compute firewall-rules list --filter='NETWORK=chap7'
NAME NETWORK DIRECTION PRIORITY ALLOW DENY
private-ssh chap7 INGRESS 1000 tcp:22
public-http chap7 INGRESS 1000 tcp:80
public-ssh chap7 INGRESS 1000 tcp:22
将你的 SSH 公钥添加到 GCP
在启动 VM 实例之前,你需要上传你的 SSH 公钥,以便登录到 VM。如果你没有任何 SSH 密钥,你需要运行ssh-keygen命令生成一对密钥(公钥和私钥)。假设你有一个公钥~/.ssh/id_rsa.pub和一个私钥~/.ssh/id_rsa
- 使用
whoami命令检查你的登录用户名,然后使用gcloud compute config-ssh通过以下命令上传你的密钥:
$ whoami
saito
$ gcloud compute config-ssh --ssh-key-file=~/.ssh/id_rsa
- 检查你的 SSH 公钥是否已作为元数据注册:
$ gcloud compute project-info describe --format=json
{
"commonInstanceMetadata": {
"fingerprint": "fAqDGp0oSMs=",
"items":
{
"key": "ssh-keys",
"value": "saito:ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAr1cHrrONuaPgN20sXCPH8uT2lOjWRB3zEncOTxOI2lCW6DM6Mr31boboDe0kAtUMdoDU43yyMe4r734SmtMuh...
就这样。这些是启动 VM 实例所需的最小配置。那么,让我们在这个基础设施上启动一些 VM 实例。
工作原理...
现在你有了自己的 VPC、子网和防火墙规则。这些基础设施将被计算引擎(VM 实例)、Kubernetes 引擎和一些其他 GCP 产品使用。让我们将两个 VM 实例部署到你的 VPC 中,看看它是如何工作的,正如下面的图所示:

SSH 连接到公共 VM 实例
- 从
chap7-public通过内部 IP 地址 ssh 连接到chap7-private:

ssh 连接到私有 VM 实例
- 输入
exit命令返回到chap7-public主机,然后使用apt-get命令安装nginx:

在公共 VM 实例上安装 nginx
- 使用以下命令启动
nginx:
$ sudo systemctl start nginx
- 使用 Web 浏览器通过外部 IP 访问
chap7-public:

访问公共 VM 实例上的 nginx Web 服务器
恭喜!你已经完成了 GCP VPC、子网和防火墙规则的设置,并启动了 VM 实例!这些是 Google Compute Engine 的基本和常见用法。你可以登录并在这些机器上安装软件,甚至从零开始构建 Kubernetes 集群。然而,GCP 还提供了一种托管的 Kubernetes 产品,叫做 Kubernetes Engine。我们将在本章中探讨它。
玩转 Google Kubernetes Engine
Kubernetes 是由 Google 设计的,并且在 Google 内部广泛使用多年。Google Cloud Platform 提供了托管的 GKE。使用 GKE,我们无需从零构建集群,而是可以按需启动和关闭集群。
准备工作
我们可以在 GCP 控制台中的 Kubernetes Engine 仪表盘或通过 gcloud CLI 启动并配置集群。使用控制台非常直接且直观。然而,使用 CLI 是一种更灵活的方法,可以使操作可重复执行,或将其与现有管道集成。在本食谱中,我们将介绍如何使用 gcloud 启动并设置 Kubernetes 集群,以及 GCP 中的一些重要概念。
在 GCP 中,一切都与项目相关联。GCP 项目是使用 GCP 服务、计费和权限控制的基本单位。一开始,我们需要从 GCP 控制台创建一个项目:console.cloud.google.com。
项目 ID 在 GCP 中是全局唯一的。项目创建完成后,我们会看到分配了一个唯一的项目编号。在首页仪表盘中,我们可以清晰地看到已使用的资源数量。我们可以在这里设置权限、存储、网络、计费和其他资源。在继续之前,我们需要安装 gcloud。gcloud 是 Google Cloud SDK 的一部分。除了 gcloud,它可以执行 GCP 中大多数常见操作,Google Cloud SDK 还包括其他常见的 GCP 工具,例如 gsutil(用于管理 Cloud Storage)、bq(BigQuery 的命令行工具)和 core(Cloud SDK 库)。这些工具可以在 Google Cloud SDK 下载页面获取:cloud.google.com/sdk/docs/#install_the_latest_cloud_tools_version_cloudsdk_current_version。
安装 gcloud 后,运行 gcloud init 进行登录,设置身份并创建一个名为 k8s-cookbook-2e 的项目。我们可以使用 gcloud 来操作 Google Cloud 中几乎所有的服务;主要的命令组是:
gcloud container [builds|clusters|images|node-pools|operations] | $COMMAND $FLAG…
gcloud 容器命令行集用于管理我们在 Google Kubernetes Engine 中的容器和集群。在启动集群时,最重要的参数是网络设置。让我们花一些时间来理解 GCP 中的网络术语。和 AWS 一样,GCP 也有 VPC 概念。这是一种更私密和安全的方式,用来将计算、存储和云资源与公共互联网隔离。它可以跨项目建立对等连接,或与本地数据中心通过 VPN 建立连接,创建混合云环境:
// create GCP VPC, it might take few minutes.
# gcloud compute networks create k8s-network
Created [https://www.googleapis.com/compute/v1/projects/kubernetes-cookbook/global/networks/k8s-network].
NAME SUBNET_MODE BGP_ROUTING_MODE IPV4_RANGE GATEWAY_IPV4
k8s-network AUTO REGIONAL
该网络上的实例在防火墙规则创建之前是无法访问的。举个例子,你可以通过运行以下命令允许所有内部流量在实例之间传递,同时允许 SSH、RDP 和 ICMP:
$ gcloud compute firewall-rules create <FIREWALL_NAME> --network k8s-network --allow tcp,udp,icmp --source-ranges <IP_RANGE>
$ gcloud compute firewall-rules create <FIREWALL_NAME> --network k8s-network --allow tcp:22,tcp:3389,icmp
默认情况下,VPC 是以自动模式创建的,这将为每个区域创建一个子网。我们可以通过子命令 describe 来观察这一点:
// gcloud compute networks describe <VPC name>
# gcloud compute networks describe k8s-network
autoCreateSubnetworks: true
creationTimestamp: '2018-02-25T13:54:28.867-08:00'
id: '1580862590680993403'
kind: compute#network
name: k8s-network
routingConfig:
routingMode: REGIONAL
selfLink: https://www.googleapis.com/compute/v1/projects/kubernetes-cookbook/global/networks/k8s-network
subnetworks:
- https://www.googleapis.com/compute/v1/projects/kubernetes-cookbook/regions/australia-southeast1/subnetworks/k8s-network
- https://www.googleapis.com/compute/v1/projects/kubernetes-cookbook/regions/europe-west4/subnetworks/k8s-network
- https://www.googleapis.com/compute/v1/projects/kubernetes-cookbook/regions/northamerica-northeast1/subnetworks/k8s-network
- https://www.googleapis.com/compute/v1/projects/kubernetes-cookbook/regions/europe-west1/subnetworks/k8s-network
- https://www.googleapis.com/compute/v1/projects/kubernetes-cookbook/regions/southamerica-east1/subnetworks/k8s-network
- https://www.googleapis.com/compute/v1/projects/kubernetes-cookbook/regions/us-central1/subnetworks/k8s-network
- https://www.googleapis.com/compute/v1/projects/kubernetes-cookbook/regions/us-east1/subnetworks/k8s-network
- https://www.googleapis.com/compute/v1/projects/kubernetes-cookbook/regions/asia-east1/subnetworks/k8s-network
- https://www.googleapis.com/compute/v1/projects/kubernetes-cookbook/regions/us-west1/subnetworks/k8s-network
- https://www.googleapis.com/compute/v1/projects/kubernetes-cookbook/regions/europe-west3/subnetworks/k8s-network
- https://www.googleapis.com/compute/v1/projects/kubernetes-cookbook/regions/asia-southeast1/subnetworks/k8s-network
- https://www.googleapis.com/compute/v1/projects/kubernetes-cookbook/regions/us-east4/subnetworks/k8s-network
- https://www.googleapis.com/compute/v1/projects/kubernetes-cookbook/regions/europe-west2/subnetworks/k8s-network
- https://www.googleapis.com/compute/v1/projects/kubernetes-cookbook/regions/asia-northeast1/subnetworks/k8s-network
- https://www.googleapis.com/compute/v1/projects/kubernetes-cookbook/regions/asia-south1/subnetworks/k8s-network
x_gcloud_bgp_routing_mode: REGIONAL
x_gcloud_subnet_mode: AUTO
在 GCP 中,每个子网跨越一个区域。区域是区域中的一个隔离位置,类似于 AWS 中的可用区概念。
或者,你可以通过添加参数 --subnet-mode=custom 来创建自定义模式的网络,这样可以定义你所需的 IP 范围、区域和所有路由规则。更多细节请参考上一节。
自动模式还可以帮助你设置所有默认的路由规则。路由用于定义特定 IP 范围的目的地。例如,此路由将数据包引导到虚拟网络 10.158.0.0/20:

默认路由示例
路由用于将数据包引导到外部世界。此路由的下一跳是默认的互联网网关,类似于 AWS 中的 igw。然而,在 GCP 中,你不需要显式地创建互联网网关:

用于互联网访问的默认路由
GCP 网络中的另一个重要概念是防火墙规则,用于控制实例的入站和出站流量。在 GCP 中,防火墙规则与虚拟机实例之间的关联通过网络标签实现。
防火墙规则也可以分配给网络中的所有实例或具有特定服务账户的实例组(仅限入站)。服务账户是 GCP 中虚拟机实例的身份。可以为服务账户分配一个或多个角色,以便它能够访问其他 GCP 资源。这类似于 AWS 中的实例配置文件。
一个虚拟机实例可以拥有多个网络标签,这意味着可以应用多个网络路由。此图显示了标签是如何工作的。在下图中,第一个防火墙规则应用于 VM1 和 VM2,VM2 与两个防火墙规则相关联:

AWS 安全组和 GCP 防火墙规则示意图
在AWS中,一个或多个入站/出站规则被定义在安全组中,且一个或多个安全组可以分配给一个EC2实例。而在GCP中,定义一个或多个防火墙规则,这些规则与一个或多个标签相关联。一个或多个标签可以分配给一个实例。通过映射网络标签,防火墙规则可以控制和限制进出实例的访问。
如何操作…
我们已经了解了 GCP 中的基本网络概念。现在让我们启动我们的第一个 GKE 集群:
| 参数 | 描述 | 示例中的值 |
|---|---|---|
--cluster-version |
支持的集群版本(请参阅cloud.google.com/kubernetes-engine/release-notes) |
1.9.2-gke.1 |
--machine-type |
节点的实例类型(请参阅cloud.google.com/compute/docs/machine-types) |
f1-micro |
--num-nodes |
集群中的节点数量 | 3 |
--network |
目标 VPC 网络 | k8s-network(我们刚刚创建的网络) |
--zone |
目标区域 | us-central1-a(你可以自由选择任何区域) |
--tags |
要附加到节点的网络标签 | private |
--service-account | --scopes |
节点身份(有关更多范围值,请参见cloud.google.com/sdk/gcloud/reference/container/clusters/create) |
storage-rw,compute-ro |
参考前面的参数,我们通过gcloud命令启动一个三节点的集群:
// create GKE cluster
$ gcloud container clusters create my-k8s-cluster --cluster-version 1.9.2-gke.1 --machine-type f1-micro --num-nodes 3 --network k8s-network --zone us-central1-a --tags private --scopes=storage-rw,compute-ro
WARNING: The behavior of --scopes will change in a future gcloud release: service-control and service-management scopes will no longer be added to what is specified in --scopes. To use these scopes, add them explicitly to --scopes. To use the new behavior, set container/new_scopes_behavior property (gcloud config set container/new_scopes_behavior true).
WARNING: Starting in Kubernetes v1.10, new clusters will no longer get compute-rw and storage-ro scopes added to what is specified in --scopes (though the latter will remain included in the default --scopes). To use these scopes, add them explicitly to --scopes. To use the new behavior, set container/new_scopes_behavior property (gcloud config set container/new_scopes_behavior true).
Creating cluster my-k8s-cluster...done.
Created [https://container.googleapis.com/v1/projects/kubernetes-cookbook/zones/us-central1-a/clusters/my-k8s-cluster].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-central1-a/my-k8s-cluster?project=kubernetes-cookbook
kubeconfig entry generated for my-k8s-cluster.
NAME LOCATION MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS
my-k8s-cluster us-central1-a 1.9.2-gke.1 35.225.24.4 f1-micro 1.9.2-gke.1 3 RUNNING
集群启动并运行后,我们可以通过配置kubectl开始连接到集群:
# gcloud container clusters get-credentials my-k8s-cluster --zone us-central1-a --project kubernetes-cookbook
Fetching cluster endpoint and auth data.
kubeconfig entry generated for my-k8s-cluster.
让我们查看集群是否健康:
// list cluster components
# kubectl get componentstatuses
NAME STATUS MESSAGE ERROR
controller-manager Healthy ok
scheduler Healthy ok
etcd-0 Healthy {"health": "true"}
etcd-1 Healthy {"health": "true"}
我们可以检查集群中的节点:
// list the nodes in cluster
# kubectl get nodes
NAME STATUS ROLES AGE VERSION
gke-my-k8s-cluster-default-pool-7d0359ed-0rl8 Ready <none> 21m v1.9.2-gke.1
gke-my-k8s-cluster-default-pool-7d0359ed-1s2v Ready <none> 21m v1.9.2-gke.1
gke-my-k8s-cluster-default-pool-7d0359ed-61px Ready <none> 21m v1.9.2-gke.1
我们还可以使用kubectl检查集群信息:
// list cluster info
# kubectl cluster-info
Kubernetes master is running at https://35.225.24.4
GLBCDefaultBackend is running at https://35.225.24.4/api/v1/namespaces/kube-system/services/default-http-backend:http/proxy
Heapster is running at https://35.225.24.4/api/v1/namespaces/kube-system/services/heapster/proxy
KubeDNS is running at https://35.225.24.4/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
kubernetes-dashboard is running at https://35.225.24.4/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy
Metrics-server is running at https://35.225.24.4/api/v1/namespaces/kube-system/services/https:metrics-server:/proxy
它是如何工作的…
在幕后,gcloud 创建了一个包含三个节点的 Kubernetes 集群,以及一个控制器管理器、调度器和两个成员的 etcd 集群。我们还可以看到主节点启动了一些服务,包括控制器使用的默认后端、用于监控的 heapster、集群中的 KubeDNS 用于 DNS 服务、用于 Kubernetes UI 的仪表盘,以及用于资源使用度量的 metrics-server。
我们看到Kubernetes-dashboard有一个 URL;让我们尝试访问它:

禁止访问 Kubernetes 仪表盘
我们得到了HTTP 403 Forbidden。那么我们从哪里获得访问权限和凭证呢?一种方法是通过kubectl proxy命令运行代理。它会将主节点 IP 绑定到本地127.0.0.1:8001:
# kubectl proxy
Starting to serve on 127.0.0.1:8001
之后,当我们访问http://127.0.0.1:8001/ui时,它将被重定向到http://127.0.0.1:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy。
自 Kubernetes 1.7 起,仪表盘支持基于持有令牌或Kubeconfig文件的用户认证:

登录到 Kubernetes 控制台
你可以创建一个用户并将其绑定到当前的上下文(请参考第八章中的 身份验证与授权 配方,高级集群管理)。为了方便起见,我们可以检查是否已有现有用户。首先,我们需要知道当前上下文的名称。上下文结合了集群信息、用于身份验证的用户和命名空间:
// check our current context name
# kubectl config current-context
gke_kubernetes-cookbook_us-central1-a_my-k8s-cluster
知道了上下文名称后,我们可以通过 kubectl 配置视图 $CONTEXT_NAME 来描述它:
// kubectl config view $CONTEXT_NAME
# kubectl config view gke_kubernetes-cookbook_us-central1-a_my-k8s-cluster
current-context: gke_kubernetes-cookbook_us-central1-a_my-k8s-cluster
kind: Config
preferences: {}
users:
- name: gke_kubernetes-cookbook_us-central1-a_my-k8s-cluster
user:
auth-provider:
config:
access-token: $ACCESS_TOKEN
cmd-args: config config-helper --format=json
cmd-path: /Users/chloelee/Downloads/google-cloud-sdk-2/bin/gcloud
expiry: 2018-02-27T03:46:57Z
expiry-key: '{.credential.token_expiry}'
token-key: '{.credential.access_token}'
name: gcp
我们可能会发现集群中存在一个默认用户;使用其$ACCESS_TOKEN,你可以查看 Kubernetes 控制台。

Kubernetes 控制台概览
我们的 GKE 集群已经启动并运行!让我们尝试看看能否在其上运行一个简单的部署:
# kubectl run nginx --image nginx --replicas=2
deployment "nginx" created
# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-8586cf59-x27bj 1/1 Running 0 12s
nginx-8586cf59-zkl8j 1/1 Running 0 12s
让我们检查一下 Kubernetes 控制台:

Kubernetes 控制台中的工作负载
耶!部署已创建,结果有两个 Pod 被调度并创建。
另请参见
-
第八章中的 kubeconfig 中的高级设置,高级集群管理
-
第八章中的 在节点中设置资源,高级集群管理
-
第八章中的 玩转 Web UI,高级集群管理
-
第八章中的 在 Kubernetes 集群中设置 DNS 服务器,高级集群管理
-
第八章中的 身份验证与授权,高级集群管理
在 GKE 上探索 CloudProvider
GKE 作为本地 Kubernetes 云提供商,与 Kubernetes 中的资源无缝集成,允许按需提供资源,例如网络的 VPC 路由、StorageClass 的 持久磁盘(PD)、服务的 L4 负载均衡器以及入口的 L4 负载均衡器。
准备就绪
默认情况下,当你在 Google Cloud Platform 中创建网络并启动 Kubernetes 集群并配置适当的路由时,容器之间就可以无需显式设置网络而相互通信。除了之前列出的资源外,在大多数情况下我们不需要显式设置任何配置。GKE 将自动工作。
如何做……
让我们看看 GKE 在存储、网络等方面提供了多少便利。
StorageClass
在 第二章中,Kubernetes 概念简介,我们学习了如何声明 PersistentVolume 和 PersistentVolumeClaim。通过动态供给,你可以定义一组不同物理存储后端的 StorageClass,并在 PersistentVolume 或 PersistentVolumeClaim 中使用它们。让我们看看它是如何工作的。
要检查当前的默认 StorageClass,可以使用 kubectl get storageclasses 命令:
# kubectl get storageclasses
NAME PROVISIONER AGE
standard (default) kubernetes.io/gce-pd 1h
我们可以看到有一个名为 standard 的默认存储类,其提供者是 GCE PD。
让我们创建一个 PersistentVolumeClaim 请求,并使用标准的 StorageClass 作为后端:
# cat gke-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-example-pv
spec:
storageClassName: standard
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
// create resources
# kubectl create -f gke-pvc.yaml
persistentvolumeclaim "pvc-example-pv" created
storageClassName 是指定 StorageClass 名称的地方。如果填写一个不存在的名称,PVC 将无法创建,因为没有适当的映射 StorageClass 可以使用:
// check pvc status
# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-example-pv Bound pvc-1491b08e-1cfc-11e8-8589-42010a800360 10Gi RWO standard 12m
// describe the details of created PVC
# kubectl describe pvc pvc-example-pv
Name: pvc-example-pv
Namespace: default
StorageClass: standard
Status: Bound
Volume: pvc-1491b08e-1cfc-11e8-8589-42010a800360
Labels: <none>
Annotations: pv.kubernetes.io/bind-completed=yes
pv.kubernetes.io/bound-by-controller=yes
volume.beta.kubernetes.io/storage-provisioner=kubernetes.io/gce-pd
Finalizers: []
Capacity: 10Gi
Access Modes: RWO
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ProvisioningSucceeded 12m persistentvolume-controller Successfully provisioned volume pvc-1491b08e-1cfc-11e8-8589-42010a800360 using kubernetes.io/gce-pd
我们可以看到卷 pvc-1491b08e-1cfc-11e8-8589-42010a800360 已经创建并绑定。如果列出 GCP 磁盘,我们会发现创建了一个持久磁盘;磁盘名称的后缀表示 Kubernetes 中的卷名称。这就是动态卷提供的魔力:
# gcloud compute disks list
NAME ZONE SIZE_GB TYPE STATUS
gke-my-k8s-cluster-5ef-pvc-1491b08e-1cfc-11e8-8589-42010a800360 us-central1-a 10 pd-standard READY
除了默认的 StorageClass,你还可以创建自己的存储类。回顾一下第二章,Kubernetes 概念详解。
服务(LoadBalancer)
LoadBalancer 类型的服务只在支持外部负载均衡器的云环境中工作。这允许外部流量路由到目标 Pod。在 GCP 中,LoadBalancer 类型的服务将创建一个 TCP 负载均衡器:
- 用于允许负载均衡器与节点之间流量的防火墙规则将自动创建:
// leveraging LoadBalancer service
# cat gke-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 1
selector:
matchLabels:
run: nginx
template:
metadata:
labels:
run: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
ports:
- port: 80
targetPort: 80
type: LoadBalancer
selector:
run: nginx
// create resources
# kubectl create -f gke-service.yaml
deployment "nginx" created
service "nginx" created
- 让我们检查服务。如果负载均衡器仍在配置中,
EXTERNAL-IP将显示<pending>。稍等片刻,负载均衡器的 IP 会最终显示出来:
# kubectl get svc nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx LoadBalancer 10.35.250.183 35.225.223.151 80:30383/TCP 11m
- 让我们用
curl测试$EXTERNAL-IP:80,看看它是否正常工作:
# curl -I 35.225.223.151
HTTP/1.1 200 OK
Server: nginx/1.13.9
Date: Thu, 01 Mar 2018 03:57:05 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 20 Feb 2018 12:21:20 GMT
Connection: keep-alive
ETag: "5a8c12c0-264"
Accept-Ranges: bytes
- 如果我们检查 GCP 中的转发规则,我们可以找到一条规则,定义了流量如何从外部 IP 转发到目标池:
# gcloud compute forwarding-rules list
NAME REGION IP_ADDRESS IP_PROTOCOL TARGET
ae1f2ad0c1d0211e8858942010a80036 us-central1 35.225.223.151 TCP us-central1/targetPools/ae1f2ad0c1d0211e8858942010a80036
- 目标池是一组实例,这些实例接收来自转发规则的流量。我们也可以使用 gcloud 命令检查目标池:
// list target pools
# gcloud compute target-pools list
NAME REGION SESSION_AFFINITY BACKUP HEALTH_CHECKS
ae1f2ad0c1d0211e8858942010a80036 us-central1 NONE k8s-1a4c86537c370d21-node
// check target pools info, replace $GCP_REGION as your default region.
# gcloud compute target-pools describe ae1f2ad0c1d0211e8858942010a80036 --region=$GCP_REGION
creationTimestamp: '2018-02-28T19:45:46.052-08:00'
description: '{"kubernetes.io/service-name":"default/nginx"}'
healthChecks:
- https://www.googleapis.com/compute/v1/projects/kubernetes-cookbook/global/httpHealthChecks/k8s-1a4c86537c370d21-node
id: '3515096241941432709'
instances:
- https://www.googleapis.com/compute/v1/projects/kubernetes-cookbook/zones/us-central1-a/instances/gke-my-k8s-cluster-default-pool-36121894-71wg
- https://www.googleapis.com/compute/v1/projects/kubernetes-cookbook/zones/us-central1-a/instances/gke-my-k8s-cluster-default-pool-36121894-04rv
- https://www.googleapis.com/compute/v1/projects/kubernetes-cookbook/zones/us-central1-a/instances/gke-my-k8s-cluster-default-pool-36121894-3mxm
kind: compute#targetPool
name: ae1f2ad0c1d0211e8858942010a80036
region: https://www.googleapis.com/compute/v1/projects/kubernetes-cookbook/regions/us-central1
selfLink: https://www.googleapis.com/compute/v1/projects/kubernetes-cookbook/regions/us-central1/targetPools/ae1f2ad0c1d0211e8858942010a80036
sessionAffinity: NONE
我们可以看到目标池内有三个节点。这些节点与我们 Kubernetes 集群中的三个节点相同。负载均衡器将根据源/定义的 IP 和端口的哈希值将流量分发到某个节点。虽然 LoadBalancer 类型的服务看起来很方便,但它无法执行基于路径的路由。此时,Ingress 就派上用场了。Ingress 支持虚拟主机、基于路径的路由和 TLS 终止,这是对 Web 服务更灵活的处理方式。
Ingress(入口)
在第五章,构建持续交付管道,我们学习了入口(Ingress)的概念,以及何时和如何使用它。Ingress 定义了一组规则,允许入站连接访问 Kubernetes 集群服务。它在 L7 层路由流量到集群,控制器将流量带到节点。当 GCP 是云服务提供商时,如果创建了 Ingress,还会创建 L7 负载均衡器,以及相关的防火墙规则、健康检查、后端服务、转发规则和 URL 映射。GCP 中的 URL 映射是一个包含一组规则的机制,将请求转发到相应的后端服务。
在这个食谱中,我们将重用来自 第五章的示例,构建持续交付流水线,Nodeport-deployment.yaml 和 echoserver.yaml。接下来是这两个服务在 第五章,构建持续交付流水线 中的工作原理图示:

Ingress 图示
我们将为 nginx 和 echoserver 创建一个 ingress,路由到不同的服务。当流量到达时,pod ingress 控制器将决定路由到哪个服务。
这里是 ingress 的示例。请注意,如果你希望底层服务始终从某个特定主机名访问,你可能需要在规则部分添加主机名:
# cat INGRESS.yaml
apiVersion: extensions/v1beta1
kind: INGRESS
metadata:
name: my-INGRESS
annotations:
INGRESS.kubernetes.io/rewrite-target: /
spec:
rules:
- http:
paths:
- path: /
# default backend
backend:
serviceName: nodeport-svc
servicePort: 8080
- path: /nginx
# nginx service
backend:
serviceName: nodeport-svc
servicePort: 8080
- path: /echoserver
# echoserver service
backend:
serviceName: echoserver-svc
servicePort: 8080
// create nodeport-svc (nginx) service
# kubectl create -f nodeport-deployment.yaml
deployment "nodeport-deploy" created
service "nodeport-svc" created
// create echoserver-svc (echoserver) service
# kubectl create -f echoserver.yaml
deployment "echoserver-deploy" created
service "echoserver-svc" created
// create INGRESS
# kubectl create -f INGRESS.yaml
INGRESS "my-INGRESS" created
请再次确认底层服务已配置为 NodePort 类型。否则,你可能会遇到如 googleapi: Error 400: Invalid value for field 'namedPorts[1].port': '0'. Must be greater than or equal to 1, invalid error 来自 loadbalancer-controller 的错误。
几分钟后,L7 负载均衡器将被创建,你可以通过 GCP 控制台或使用 gcloud 命令查看它。让我们使用 kubectl 检查 INGRESS 中的后端服务是否健康:
// kubectl describe INGRESS $INGRESS_name
# kubectl describe INGRESS my-INGRESS
curl Name: my-INGRESS
Namespace: default
Address: 35.190.46.137
Default backend: default-http-backend:80 (10.32.2.3:8080)
Rules:
Host Path Backends
---- ---- --------
*
/ nodeport-svc:8080 (<none>)
/nginx nodeport-svc:8080 (<none>)
/echoserver echoserver-svc:8080 (<none>)
Annotations:
backends: {"k8s-be-31513--91cf30ccf285becb":"HEALTHY","k8s-be-31780--91cf30ccf285becb":"HEALTHY","k8s-be-32691--91cf30ccf285becb":"HEALTHY"}
forwarding-rule: k8s-fw-default-my-INGRESS--91cf30ccf285becb
rewrite-target: /
target-proxy: k8s-tp-default-my-INGRESS--91cf30ccf285becb
url-map: k8s-um-default-my-INGRESS--91cf30ccf285becb
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Service 2m (x11 over 1h) loadbalancer-controller no user specified default backend, using system default
我们可以看到三个后端是健康的,相关的转发规则、目标代理和 URL 映射都已创建。我们可以通过访问 GKE 中的发现和负载均衡,或通过网络服务中的负载均衡选项卡,从 GCP 控制台获取全面的视图:

发现与负载均衡
后端在这里进行说明:

后端服务
有时,你的 ingress 资源可能会遇到更新。当你重新部署时,无法保证 GCP 会为你的负载均衡器分配相同的 IP 地址。这可能会在 IP 地址与 DNS 名称关联时引发问题。每次 IP 地址更改时,都需要更新目标 IP 地址。可以通过静态外部 IP 地址加上 kubernetes.io/INGRESS.global-static-ip-name 注解来解决这个问题:
// allocate static IP as my-external-ip
# gcloud compute addresses create my-external-ip –global
// check external-ip
# gcloud compute addresses list
NAME REGION ADDRESS STATUS
my-external-ip 130.211.37.61 RESERVED
After external IP is prepared, we could start launching our INGRESS now.
# cat INGRESS-static-ip.yaml
apiVersion: extensions/v1beta1
kind: INGRESS
metadata:
name: my-INGRESS-static-ip
annotations:
INGRESS.kubernetes.io/rewrite-target: /
kubernetes.io/INGRESS.global-static-ip-name: my-external-ip
spec:
rules:
- http:
paths:
- path: /
# default backend
backend:
serviceName: nodeport-svc
servicePort: 8080
- path: /nginx
# nginx service
backend:
serviceName: nodeport-svc
servicePort: 8080
- path: /echoserver
# echoserver service
backend:
serviceName: echoserver-svc
servicePort: 8080
# kubectl create -f INGRESS-static-ip.yaml
INGRESS "my-INGRESS-stati-ip" created
让我们描述 my-INGRESS,看看它是否与我们创建的外部 IP 正确绑定:
# kubectl describe INGRESS my-INGRESS
Name: my-INGRESS
Namespace: default
Address: 130.211.37.61
Default backend: default-http-backend:80 (10.32.2.3:8080)
Rules:
Host Path Backends
---- ---- --------
* / nodeport-svc:8080 (<none>)
/nginx nodeport-svc:8080 (<none>) /echoserver echoserver-svc:8080 (<none>)Annotations:
backends: {"k8s-be-31108--91cf30ccf285becb":"HEALTHY","k8s-be-31250--91cf30ccf285becb":"HEALTHY","k8s-be-32691--91cf30ccf285becb":"HEALTHY"} forwarding-rule: k8s-fw-default-my-INGRESS--91cf30ccf285becb rewrite-target: / target-proxy: k8s-tp-default-my-INGRESS--91cf30ccf285becb url-map: k8s-um-default-my-INGRESS--91cf30ccf285becbEvents: Type Reason Age From Message ---- ------ ---- ---- ------- Normal ADD 27m loadbalancer-controller default/my-INGRESS Normal CREATE 25m loadbalancer-controller ip: 130.211.37.61
Normal Service 4m (x6 over 25m) loadbalancer-controller no user specified default backend, using system default
我们已经准备好了。Nginx 和 echoserver 可以通过外部静态 IP 130.211.37.61 进行访问,我们可以通过 GCP 的云 DNS 服务将其与 DNS 名称关联。
还有更多内容……
在 Kubernetes v.1.9 中,Kubernetes 云控制器管理器被提升为 Alpha 版本。云控制器管理器旨在通过其自身的发布周期,使云提供商发布功能支持,这些支持可以独立于 Kubernetes 发布周期。随后,它也可以与 Kubernetes 核心发布周期独立。它提供了每个云提供商可以实现的公共接口,从而与 Kubernetes 核心逻辑解耦。未来,我们将看到来自不同云提供商的更全面支持!
另见
-
第二章中的与服务协作,深入理解 Kubernetes 概念
-
第二章中的与卷协作,深入理解 Kubernetes 概念
-
第三章中的转发容器端口,玩转容器
在 GKE 上管理 Kubernetes 集群
Google Kubernetes Engine 为我们提供了运行 Kubernetes 的无缝体验;它还使 Kubernetes 管理变得非常简单。根据预期的高峰时段,我们可能需要扩展或缩减 Kubernetes 节点。或者,我们可以使用 Autoscaler 为节点进行自动扩展。Kubernetes 是一个不断发展的平台,发布速度很快。我们可能需要时不时地升级集群版本,这非常简单。我们还可以使用 Autoupgrade 功能,通过启用 GKE 中的自动调度功能来升级集群。让我们看看如何操作。
准备工作
在设置 GCP 提供的管理功能之前,我们必须先启动并运行集群。我们将重用本章中在《玩转 Google Kubernetes Engine》一节中创建的集群。
如何操作…
在本食谱中,我们将介绍如何根据使用情况和需求管理节点数。同时,我们将学习如何处理集群升级。最后,我们将了解如何在 GKE 中配置一个多区集群,以防止物理区域故障。
节点池
节点池是 GCP 中一组共享相同配置的实例。当我们通过gcloud命令启动集群时,我们传递--num-node=3及其余参数。然后,三台实例将在同一池中启动,分享相同配置,使用相同的方法:
# gcloud compute instance-groups list NAME LOCATION SCOPE NETWORK MANAGED INSTANCES gke-my-k8s-cluster-default-pool-36121894-grp us-central1-a zone k8s-network Yes 3
假设你的服务有一个预期的高峰时段。作为 Kubernetes 管理员,你可能希望调整集群中节点池的规模。
# gcloud container clusters resize my-k8s-cluster --size 5 --zone us-central1-a --node-pool default-pool
Pool [default-pool] for [my-k8s-cluster] will be resized to 5.
Do you want to continue (Y/n)? y
Resizing my-k8s-cluster...done.
Updated [https://container.googleapis.com/v1/projects/kubernetes-cookbook/zones/us-central1-a/clusters/my-k8s-cluster].
# kubectl get nodes
NAME STATUS ROLES AGE VERSION
gke-my-k8s-cluster-default-pool-36121894-04rv Ready <none> 6h v1.9.2-gke.1
gke-my-k8s-cluster-default-pool-36121894-71wg Ready <none> 6h v1.9.2-gke.1
gke-my-k8s-cluster-default-pool-36121894-8km3 Ready <none> 39s v1.9.2-gke.1
gke-my-k8s-cluster-default-pool-36121894-9j9p Ready <none> 31m v1.9.2-gke.1
gke-my-k8s-cluster-default-pool-36121894-9jmv Ready <none> 36s v1.9.2-gke.1
resize 命令可以帮助你扩展和缩减。如果调整大小后节点数量少于调整前,调度程序将把 Pod 迁移到可用节点上运行。
你可以在规格中为每个容器设置计算资源的边界。你为 Pod 容器设置请求和限制。假设我们有一个超级 nginx,它需要 1024 MB 内存:
# cat super-nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: super-nginx
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
resources:
requests:
memory: 1024Mi
// create super nginx deployment
# kubectl create -f super-nginx.yaml
deployment "super-nginx" created
# kubectl get pods
NAME READY STATUS RESTARTS AGE
super-nginx-df79db98-5vfmv 0/1 Pending 0 10s
# kubectl describe po super-nginx-779494d88f-74xjp
Name: super-nginx-df79db98-5vfmv
Namespace: default
Node: <none>
Labels: app=nginx
pod-template-hash=89358654
Annotations: kubernetes.io/limit-ranger=LimitRanger plugin set: cpu request for container nginx
Status: PendingIP:
Controlled By: ReplicaSet/super-nginx-df79db98
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 11s (x5 over 18s) default-scheduler 0/5 nodes are available: 5 Insufficient memory.
我们创建的节点大小是 f1-micro,每个节点只有 0.6 GB 内存。这意味着调度器永远找不到具有足够内存来运行 super-nginx 的节点。在这种情况下,我们可以通过创建另一个节点池,向集群中添加更多内存更大的节点。我们以 g1-small 为例,它包含 1.7 GB 内存:
// create a node pool named larger-mem-pool with n1-standard-1 instance type
# gcloud container node-pools create larger-mem-pool --cluster my-k8s-cluster --machine-type n1-standard-1 --num-nodes 2 --tags private --zone us-central1-a --scopes=storage-rw,compute-ro
...
Creating node pool larger-mem-pool...done.
Created [https://container.googleapis.com/v1/projects/kubernetes-cookbook/zones/us-central1-a/clusters/my-k8s-cluster/nodePools/larger-mem-pool].
NAME MACHINE_TYPE DISK_SIZE_GB NODE_VERSION
larger-mem-pool n1-standard-1 100 1.9.2-gke.1
// check node pools
# gcloud container node-pools list --cluster my-k8s-cluster --zone us-central1-a
NAME MACHINE_TYPE DISK_SIZE_GB NODE_VERSION
default-pool f1-micro 100 1.9.2-gke.1
larger-mem-pool n1-standard-1 100 1.9.2-gke.1
// check current nodes
# kubectl get nodes
NAME STATUS ROLES AGE VERSION
gke-my-k8s-cluster-default-pool-36121894-04rv Ready <none> 7h v1.9.2-gke.1
gke-my-k8s-cluster-default-pool-36121894-71wg Ready <none> 7h v1.9.2-gke.1
gke-my-k8s-cluster-default-pool-36121894-8km3 Ready <none> 9m v1.9.2-gke.1
gke-my-k8s-cluster-default-pool-36121894-9j9p Ready <none> 40m v1.9.2-gke.1
gke-my-k8s-cluster-default-pool-36121894-9jmv Ready <none> 9m v1.9.2-gke.1
gke-my-k8s-cluster-larger-mem-pool-a51c8da3-f1tb Ready <none> 1m v1.9.2-gke.1
gke-my-k8s-cluster-larger-mem-pool-a51c8da3-scw1 Ready <none> 1m v1.9.2-gke.1
看起来我们有了两个更强大的节点。让我们看看我们的超级 nginx 状态:
# kubectl get pods
NAME READY STATUS RESTARTS AGE
super-nginx-df79db98-5vfmv 1/1 Running 0 23m
运行了!Kubernetes 调度器将始终尝试找到足够的资源来调度 Pod。在这种情况下,集群中添加了两个新节点,可以满足资源需求,因此 Pod 被调度并运行:
// check the event of super nginx
# kubectl describe pods super-nginx-df79db98-5vfmv
...
Events:
Warning FailedScheduling 3m (x7 over 4m) default-scheduler 0/5 nodes are available: 5 Insufficient memory.
Normal Scheduled 1m default-scheduler Successfully assigned super-nginx-df79db98-5vfmv to gke-my-k8s-cluster-larger-mem-pool-a51c8da3-scw1
Normal SuccessfulMountVolume 1m kubelet, gke-my-k8s-cluster-larger-mem-pool-a51c8da3-scw1 MountVolume.SetUp succeeded for volume "default-token-bk8p2"
Normal Pulling 1m kubelet, gke-my-k8s-cluster-larger-mem-pool-a51c8da3-scw1 pulling image "nginx"
Normal Pulled 1m kubelet, gke-my-k8s-cluster-larger-mem-pool-a51c8da3-scw1 Successfully pulled image "nginx"
Normal Created 1m kubelet, gke-my-k8s-cluster-larger-mem-pool-a51c8da3-scw1 Created container
Normal Started 1m kubelet, gke-my-k8s-cluster-larger-mem-pool-a51c8da3-scw1 Started container
从 Pod 的事件中,我们可以知道它的运行路径。最初,它无法找到任何具有足够资源的节点,最终被调度到一个名为 gke-my-k8s-cluster-larger-mem-pool-a51c8da3-scw1 的新节点上。
为了让用户在调度 Pod 到特定节点时具有偏好,nodeSelector 被引入。你可以在 Pod 规范中使用内置节点标签,比如 beta.kubernetes.io/instance-type: n1-standard-1,或者使用自定义标签来实现。更多信息请参阅 kubernetes.io/docs/concepts/configuration/assign-pod-node。
Kubernetes 还支持集群自动扩展器,它可以根据容量自动调整集群大小,如果所有节点的资源不足以运行请求的 Pod。为了实现这一点,我们在创建新节点池时添加 –enable-autoscaling,并指定最大和最小节点数:
# cloud container node-pools create larger-mem-pool --cluster my-k8s-cluster --machine-type n1-standard-1 --tags private --zone us-central1-a --scopes=storage-rw,compute-ro --enable-autoscaling --min-nodes 1 --max-nodes 5
...
Creating node pool larger-mem-pool...done.
Created [https://container.googleapis.com/v1/projects/kubernetes-cookbook/zones/us-central1-a/clusters/my-k8s-cluster/nodePools/larger-mem-pool].
NAME MACHINE_TYPE DISK_SIZE_GB NODE_VERSION
larger-mem-pool n1-standard-1 100 1.9.2-gke.1
几分钟后,我们可以看到集群中有了一个新节点:
# kubectl get nodes
NAME STATUS ROLES AGE VERSION
gke-my-k8s-cluster-default-pool-36121894-04rv Ready <none> 8h v1.9.2-gke.1
gke-my-k8s-cluster-default-pool-36121894-71wg Ready <none> 8h v1.9.2-gke.1
gke-my-k8s-cluster-default-pool-36121894-8km3 Ready <none> 1h v1.9.2-gke.1
gke-my-k8s-cluster-default-pool-36121894-9j9p Ready <none> 1h v1.9.2-gke.1
gke-my-k8s-cluster-default-pool-36121894-9jmv Ready <none> 1h v1.9.2-gke.1
gke-my-k8s-cluster-larger-mem-pool-a51c8da3-s6s6 Ready <none> 15m v1.9.2-gke.1
现在,让我们通过 kubectl 编辑或创建新部署,将我们的超级 nginx 的副本从 1 改为 4:
// check current pods
# kubectl get pods
NAME READY STATUS RESTARTS AGE
super-nginx-df79db98-5q9mj 0/1 Pending 0 3m
super-nginx-df79db98-72fcz 1/1 Running 0 3m
super-nginx-df79db98-78lbr 0/1 Pending 0 3m
super-nginx-df79db98-fngp2 1/1 Running 0 3m
我们发现有两个 Pod 状态为待定:
// check nodes status
# kubectl get nodes
NAME STATUS ROLES AGE VERSION
gke-my-k8s-cluster-default-pool-36121894-04rv Ready <none> 8h v1.9.2-gke.1
gke-my-k8s-cluster-default-pool-36121894-71wg Ready <none> 8h v1.9.2-gke.1
gke-my-k8s-cluster-default-pool-36121894-9j9p Ready <none> 2h v1.9.2-gke.1
gke-my-k8s-cluster-larger-mem-pool-a51c8da3-d766 Ready <none> 4m v1.9.2-gke.1
gke-my-k8s-cluster-larger-mem-pool-a51c8da3-gtsn Ready <none> 3m v1.9.2-gke.1
gke-my-k8s-cluster-larger-mem-pool-a51c8da3-s6s6 Ready <none> 25m v1.9.2-gke.1
几分钟后,我们看到在更大的内存池中有了新成员,所有的 Pod 都开始运行:
// check pods status
# kubectl get pods
NAME READY STATUS RESTARTS AGE
super-nginx-df79db98-5q9mj 1/1 Running 0 3m
super-nginx-df79db98-72fcz 1/1 Running 0 3m
super-nginx-df79db98-78lbr 1/1 Running 0 3m
super-nginx-df79db98-fngp2 1/1 Running 0 3m
集群自动扩展器非常有用且具有成本效益。当节点过度配置时,节点池中的额外节点将被自动终止。
多区域和区域性集群
我们的 my-k8s-cluster 当前部署在 us-central1-a 区域。虽然区域是一个物理隔离的位置,但它可能会发生故障。Google Kubernetes Engine 支持多区域和区域性部署。多区域集群在一个区域创建一个主节点,并在多个区域配置节点;而区域性集群则在三个区域创建多个主节点,并在多个区域配置节点。
多区域集群
要启用多区域集群,在创建集群时在命令中添加 --additional-zones $zone2, $zone3, …。
就像 AWS 一样,GCP 也有服务配额限制。你可以使用 gcloud compute project-info describe –project $PROJECT_NAME 来检查配额,并在需要时通过 GCP 控制台请求增加配额。
先让我们每个区域启动一个两节点的集群:
// launch a multi-zone cluster with 2 nodes per zone.
# gcloud container clusters create my-k8s-cluster --cluster-version 1.9.2-gke.1 --machine-type f1-micro --num-nodes 2 --network k8s-network --tags private --scopes=storage-rw,compute-ro --zone us-central1-a --additional-zones us-central1-b,us-central1-c
Creating cluster my-k8s-cluster...done.
Created [https://container.googleapis.com/v1/projects/kubernetes-cookbook/zones/us-central1-a/clusters/my-k8s-cluster].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-central1-a/my-k8s-cluster?project=kubernetes-cookbook
kubeconfig entry generated for my-k8s-cluster.
NAME LOCATION MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS
my-k8s-cluster us-central1-a 1.9.2-gke.1 35.226.67.179 f1-micro 1.9.2-gke.1 6 RUNNING
我们发现现在有六个节点:
# kubectl get nodes
NAME STATUS ROLES AGE VERSION
gke-my-k8s-cluster-default-pool-068d31a2-q909 Ready <none> 8m v1.9.2-gke.1
gke-my-k8s-cluster-default-pool-068d31a2-rqzw Ready <none> 8m v1.9.2-gke.1
gke-my-k8s-cluster-default-pool-64a6ead8-qf6z Ready <none> 8m v1.9.2-gke.1
gke-my-k8s-cluster-default-pool-64a6ead8-x8cc Ready <none> 8m v1.9.2-gke.1
gke-my-k8s-cluster-default-pool-798c4248-2r4p Ready <none> 8m v1.9.2-gke.1
gke-my-k8s-cluster-default-pool-798c4248-skdn Ready <none> 8m v1.9.2-gke.1
让我们检查节点是否分布在我们指定的三个区域:
# gcloud compute instance-groups list NAME LOCATION SCOPE NETWORK MANAGED INSTANCES gke-my-k8s-cluster-default-pool-068d31a2-grp us-central1-a zone k8s-network Yes 2 gke-my-k8s-cluster-default-pool-64a6ead8-grp us-central1-c zone k8s-network Yes 2 gke-my-k8s-cluster-default-pool-798c4248-grp us-central1-b zone k8s-network Yes 2
区域集群
区域集群仍处于 Beta 阶段。要使用这些功能,我们必须启用 gcloud beta 命令。可以通过以下命令启用:
# export CLOUDSDK_CONTAINER_USE_V1_API_CLIENT=false # gcloud config set container/use_v1_api false
Updated property [container/use_v1_api].
然后我们应该能够使用gcloud v1beta命令来启动区域集群:
# gcloud beta container clusters create my-k8s-cluster --cluster-version 1.9.2-gke.1 --machine-type f1-micro --num-nodes 2 --network k8s-network --tags private --scopes=storage-rw,compute-ro --region us-central1
Creating cluster my-k8s-cluster...done. Created [https://container.googleapis.com/v1beta1/projects/kubernetes-cookbook/zones/us-central1/clusters/my-k8s-cluster]. To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-central1/my-k8s-cluster?project=kubernetes-cookbook
kubeconfig entry generated for my-k8s-cluster. NAME LOCATION MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS my-k8s-cluster us-central1 1.9.2-gke.1 35.225.71.127 f1-micro 1.9.2-gke.1 6 RUNNING
这个命令与创建集群的命令非常相似,只是有两个不同之处:在组名容器之前添加了一个 beta 标志,表示这是一个v1beta命令。第二个不同之处是将--zone更改为--region:
// list instance groups
# gcloud compute instance-groups list
NAME LOCATION SCOPE NETWORK MANAGED INSTANCES
gke-my-k8s-cluster-default-pool-074ab64e-grp us-central1-a zone k8s-network Yes 2
gke-my-k8s-cluster-default-pool-11492dfc-grp us-central1-c zone k8s-network Yes 2
gke-my-k8s-cluster-default-pool-f2c90100-grp us-central1-b zone k8s-network Yes 2
集群升级
Kubernetes 是一个快速发布的项目。GKE 也在不断支持新版本。每个月出现多个小版本更新并不罕见。请查看 GKE 控制台:

在 GCP 控制台中查看升级信息
我们看到升级已可用。截图中的 1.9.3-gke.1 刚刚发布,我们的集群可以进行升级:

升级可用至 1.9.3-gke.0
我们可以通过 GKE 控制台或使用 gcloud 命令来升级集群。在下一个示例中,我们将使用单区(us-central1-a)集群来演示如何升级。升级集群时,主节点始终是第一个进行升级的节点。所需的节点版本不能高于当前主节点的版本。
# gcloud container clusters upgrade my-k8s-cluster --zone us-central1-a --cluster-version 1.9.3-gke.0 –master
Master of cluster [my-k8s-cluster] will be upgraded from version
[1.9.2-gke.1] to version [1.9.3-gke.0]. This operation is long-running
and will block other operations on the cluster (including delete)
until it has run to completion.
Do you want to continue (Y/n)? y
Upgrading my-k8s-cluster...done.
Updated [https://container.googleapis.com/v1/projects/kubernetes-cookbook/zones/us-central1-a/clusters/my-k8s-cluster].
让我们检查主节点的版本:
# kubectl version
...
Server Version: version.Info{Major:"1", Minor:"9+", GitVersion:"v1.9.3-gke.0", GitCommit:"a7b719f7d3463eb5431cf8a3caf5d485827b4210", GitTreeState:"clean", BuildDate:"2018-02-16T18:26:01Z", GoVersion:"go1.9.2b4", Compiler:"gc", Platform:"linux/amd64"}
看起来不错。主节点已升级到v1.9.3-gke.0,但我们的节点还没有升级:
# kubectl get nodes
NAME STATUS ROLES AGE VERSION
gke-my-k8s-cluster-default-pool-978ca614-3jxx Ready <none> 8m v1.9.2-gke.1
gke-my-k8s-cluster-default-pool-978ca614-njrs Ready <none> 8m v1.9.2-gke.1
gke-my-k8s-cluster-default-pool-978ca614-xmlw Ready <none> 8m v1.9.2-gke.1
对于节点升级,GKE 不是一次性地升级所有节点,而是执行滚动升级。它首先会将一个节点从节点池中排空并注销,删除旧实例,并以所需的版本提供新实例,然后将其添加回集群:
// perform node upgrades.
# gcloud container clusters upgrade my-k8s-cluster --zone us-central1-a --cluster-version 1.9.3-gke.0
All nodes (3 nodes) of cluster [my-k8s-cluster] will be upgraded from
version [1.9.2-gke.1] to version [1.9.3-gke.0]. This operation is
long-running and will block other operations on the cluster (including
delete) until it has run to completion.
Do you want to continue (Y/n)? y
Upgrading my-k8s-cluster...done.
Updated [https://container.googleapis.com/v1/projects/kubernetes-cookbook/zones/us-central1-a/clusters/my-k8s-cluster].
节点池可以通过在集群创建时使用--enable-autoupgrade标志来配置为自动升级,或者使用 gcloud 容器的node-pools更新命令来更新现有节点池。有关更多信息,请参阅cloud.google.com/kubernetes-engine/docs/concepts/node-auto-upgrades。
这将需要超过 10 分钟。之后,集群中的所有节点将升级到1.9.3-gke.0。
另见
-
在第八章的在 kubeconfig 中的高级设置,高级集群管理
-
在第八章的设置节点资源中,高级集群管理
第八章:高级集群管理
本章我们将介绍以下几个小节:
-
kubeconfig 的高级设置
-
设置节点中的资源
-
玩转 WebUI
-
使用 RESTful API
-
使用 Kubernetes DNS
-
认证和授权
介绍
本章我们将讨论一些高级管理主题。首先,你将学习如何使用 kubeconfig 管理不同的集群。接着,我们将处理节点中的计算资源。Kubernetes 提供了一个友好的用户界面,展示了资源的当前状态,例如部署、节点和 Pod。你将学习如何构建和管理它。
接下来,你将学习如何使用 Kubernetes 暴露的 RESTful API。这将是与其他系统集成的一个便捷方式。最后,我们将构建一个安全的集群;最后一节将介绍如何在 Kubernetes 中设置认证和授权。
kubeconfig 的高级设置
kubeconfig 是一个配置文件,用于在 Kubernetes 中管理集群、上下文和认证设置,位于客户端。通过使用 kubeconfig 文件,我们可以设置不同的集群凭据、用户和命名空间,以便在集群或集群内切换上下文。它可以通过命令行使用 kubectl config 子命令进行配置,或者直接更新配置文件。在这一部分,我们将描述如何使用 kubectl config 操控 kubeconfig,以及如何直接输入 kubeconfig 文件。
如果你已经阅读了第二章《走进 Kubernetes 概念》中的 使用命名空间 部分,第一次提到 kubeconfig,你会了解其基本概念。让我们回顾一些要点:

kubeconfig 包含三个参数:用户、集群和上下文
从上面的图示中,我们可以注意到以下几点:
-
kubeconfig 中有三个参数:用户、集群和上下文—用户有自己的认证,集群决定特定的 API 服务器以及专用计算资源。上下文包含 用户 和集群。
-
为各种设置组合构建多个上下文:用户和集群可以在不同的上下文中共享。
-
命名空间可以在一个上下文中对齐:命名空间的当前上下文设置规则。所有请求应遵循当前上下文中的用户和集群映射。
准备就绪
请运行两个 Kubernetes 集群并为它们指定主机名。你可以在主节点上直接更新 hostfile(/etc/hosts)。一个在 localhost 上,API 服务器端点为 http://localhost:8080,另一个在远程节点上,端点为 http://$REMOTE_MASTER_NODE:8080。我们将使用这两个集群进行演示。这里的 API 服务器端点是非安全通道。这是一个简单的 API 服务器配置,用于访问权限的模拟。
在 kubeadm 上启用 API 服务器的不安全端点
在运行 kubeadm init 时,我们必须向 API 服务器传递额外的参数。在这种情况下,应应用通过标志 --config 指定的自定义配置文件:
// you can also get this file through code bundle
$ cat additional-kubeadm-config
apiVersion: kubeadm.k8s.io/v1alpha1
kind: MasterConfiguration
apiServerExtraArgs:
insecure-bind-address: "0.0.0.0"
insecure-port: "8080"
// start cluster with additional system settings
$ sudo kubeadm init --config ./additional-kubeadm-config
启动具有不安全访问 API 服务器端点的两个集群后,确保您可以在本地集群中访问它们:
// on localhost cluster, the following commands should be successful
$ curl http://localhost:8080
$ curl http://$REMOTE_MASTER_NODE:8080
请注意,不安全地址配置仅供我们接下来的教程使用。用户在实际系统中设置时应小心谨慎。
在开始之前,我们应该检查默认的 kubeconfig,以便观察更新后的变化。执行命令 kubectl config view 来查看您的初始 kubeconfig:
// the settings created by kubeadm
$ kubectl config view
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: REDACTED
server: https://192.168.122.101:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes kind: Config
preferences: {}
users:
- name: kubernetes-admin
user:
client-certificate-data: REDACTED
client-key-data: REDACTED
根据您的安装方式,可能会有一些不同的设置。但我们也可能会发现工具已经初始化了一个基本的上下文,在 kubeadm 中是 kubernetes-admin@kubernetes。接下来,继续复制物理 kubeconfig 文件,作为后续更新的基础,并且在实践后恢复原始环境时使用。
// in default, the kubeconfig used by client is the one under $HOME
$ cp ~/.kube/config ~/original-kubeconfig
如何操作...
在本教程中,我们将使用本地集群作为主要控制台,通过上下文切换集群。首先,在两个集群中运行不同数量的 nginx,并确保所有 pod 都在运行:
// in the terminal of localhost cluster
$ kubectl run local-nginx --image=nginx --replicas=2 --port=80
deployment "local-nginx" created
// check the running pods
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
local-nginx-6484bbb57d-xpjp2 1/1 Running 0 1m
local-nginx-6484bbb57d-z4qgp 1/1 Running 0 1m
// in the terminal of remote cluster
$ kubectl run remote-nginx --image=nginx --replicas=4 --port=80
deployment "remote-nginx" created
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
remote-nginx-5dd7b9cb7d-fxr9m 1/1 Running 0 29s
remote-nginx-5dd7b9cb7d-gj2ft 1/1 Running 0 29s
remote-nginx-5dd7b9cb7d-h7lmj 1/1 Running 0 29s
remote-nginx-5dd7b9cb7d-hz766 1/1 Running 0 29s
设置新凭据
接下来,我们将为每个集群设置两个凭据。使用子命令 set-credentials,命令格式为 kubectl config set-credentials <CREDENTIAL_NAME> 将凭据添加到 kubeconfig 中。Kubernetes 支持不同的身份验证方法。我们可以使用密码、客户端证书或令牌。在此示例中,我们将使用 HTTP 基本身份验证来简化场景。Kubernetes 还支持客户端证书和令牌身份验证。如需更多信息,请使用 set-credentials 命令并带上 -h 标志,详细了解其功能:
// check the details of setting up credentials
$ kubectl config set-credentials -h
// in localhost cluster, copy the based file into a new one
$ cp ~/original-kubeconfig ~/new-kubeconfig
// add a user "user-local" with credential named "myself@localhost" in kubeconfig "new-kubeconfig"
$ kubectl config set-credentials myself@localhost --username=user-local --password=passwordlocal --kubeconfig="new-kubeconfig"
User "myself@local" set.
通过前面的步骤,我们成功地在 "new-kubeconfig" kubeconfig 文件中添加了一个新凭据。默认情况下,kubeconfig 文件将以 YAML 格式进行格式化——您可以通过文本编辑器查看该文件。使用这种方法,我们能够自定义新配置,而不会干扰当前的设置。另一方面,如果没有 --kubeconfig 标志,更新将直接附加到 live kubeconfig:
// renew live kubeconfig file with previous update
$ cp ~/new-kubeconfig ~/.kube/config
// add another credential in localhost cluster, this time, let's update current settings directly
$ kubectl config set-credentials myself@remote --username=user-remote --password=passwordremote
User "myself@remote" set.
此时,检查您的实时 kubeconfig 设置,查看新凭据:
$ kubectl config view
...
users:
- name: myself@local
user:
password: passwordlocal
username: user-local
- name: myself@remote
user:
password: passwordremote
username: user-remote
设置新集群
要设置新集群,我们使用kubectl config set-cluster <CLUSTER_NAME>命令。需要使用额外的--server标志来指示访问的集群。其他标志用于定义安全级别,例如--insecure-skip-tls-verify标志,它跳过对服务器证书的检查。如果你正在设置一个可信的 HTTPS 服务器,你需要使用--certificate-authority=$PATH_OF_CERT --embed-certs=true来代替。有关更多信息,可以使用-h标志来查看命令帮助。以下命令中,我们在本地主机环境中设置了两个集群配置:
// in localhost cluster, create a cluster information pointing to itself
$ kubectl config set-cluster local-cluster --insecure-skip-tls-verify=true --server=http://localhost:8080
Cluster "local-cluster" set.
// another cluster information is about the remote one
$ kubectl config set-cluster remote-cluster --insecure-skip-tls-verify=true --server=http://$REMOTE_MASTER_NODE:8080
Cluster "remote-cluster" set.
// check kubeconfig in localhost cluster, in this example, the remote master node has the hostname "node01"
$ kubectl config view
apiVersion: v1
clusters:
...
- cluster:
insecure-skip-tls-verify: true
server: http://localhost:8080
name: local-cluster
- cluster:
insecure-skip-tls-verify: true
server: http://node01:8080
name: remote-cluster
...
我们尚未将任何内容与用户和集群关联。我们将在下一节通过上下文将它们链接起来。
设置上下文并切换当前上下文
一个上下文包含一个集群、一个命名空间和一个用户。根据当前的上下文,客户端将使用指定的用户信息和命名空间来向集群发送请求。要设置上下文,我们将使用kubectl config set-context <CONTEXT_NAME> --user=<CREDENTIAL_NAME> --namespace=<NAMESPACE> --cluster=<CLUSTER_NAME>命令来创建或更新它:
// in localhost cluster, create a context for accessing local cluster's default namespace
$ kubectl config set-context default/local/myself --user=myself@local --namespace=default --cluster=local-cluster
Context "default/local/myself" created.
// furthermore, create another context for remote cluster
$ kubectl config set-context default/remote/myself --user=myself@remote --namespace=default --cluster=remote-cluster
Context "default/remote/myself" created.
让我们检查当前的 kubeconfig。我们可以找到两个新的上下文:
$ kubectl config view
...
contexts:
- context:
cluster: local-cluster
namespace: default
user: myself@local
name: default/local/myself
- context:
cluster: remote-cluster
namespace: default
user: myself@remote
name: default/remote/myself
...
创建上下文后,我们可以切换上下文以管理不同的集群。在这里,我们将使用kubectl config use-context <CONTEXT_NAME>命令:
// check current context
$ kubectl config current-context
kubernetes-admin@kubernetes
// use the new local context instead
$ kubectl config use-context default/local/myself
Switched to context "default/local/myself".
// check resource for the status of context
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
local-nginx-6484bbb57d-xpjp2 1/1 Running 0 2h
local-nginx-6484bbb57d-z4qgp 1/1 Running 0 2h
是的,看来没问题。那如果我们切换到具有远程集群设置的上下文呢:
// switch to the context of remote cluster
$ kubectl config use-context default/remote/myself
Switched to context "default/remote/myself".
// check the pods
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
remote-nginx-5dd7b9cb7d-fxr9m 1/1 Running 0 2h
remote-nginx-5dd7b9cb7d-gj2ft 1/1 Running 0 2h
remote-nginx-5dd7b9cb7d-h7lmj 1/1 Running 0 2h
remote-nginx-5dd7b9cb7d-hz766 1/1 Running 0 2h
我们所做的所有操作都在本地主机集群中。kubeconfig 使得在多个集群和多个用户之间工作的场景变得更加简单。
清理 kubeconfig
我们仍然可以利用kubectl config来删除 kubeconfig 中的配置。对于集群和上下文,你可以使用delete-cluster和delete-context子命令删除不需要的配置。或者,对于这三类配置,unset子命令可以完成删除:
// delete the customized local context
$ kubectl config delete-cluster local-cluster
deleted cluster local-cluster from $HOME/.kube/config
// unset the local user
// to remove cluster, using property clusters.CLUSTER_NAME; to remove contexts, using property contexts.CONTEXT_NAME
$ kubectl config unset users.myself@local
Property "users.myself@local" unset.
尽管前面的命令效果会立即应用于当前的 kubeconfig,但一个更快且更可靠的方法是更新另一个 kubeconfig 文件进行替换。kubeconfig 文件是文本文件new-kubeconfig,即我们刚才更新的文件,或者是我们从初始语句original-kubeconfig中复制的文件:
// remove all of our practices
$ cp ~/original-kubeconfig ~/.kube/config
// check your kubeconfig to make sure it has been cleaned
$ kubectl config view
还有更多内容...
正如我们在前一节中提到的,涉及凭证和权限的实际用例不能忽视,就像穿越不安全的端点一样,就像我们演示中的情况。为了避免安全问题,在授予用户权限时,你可以参考官方文档(见kubernetes.io/docs/admin/authentication/)。
另见
kubeconfig 管理集群、凭证和命名空间设置。请参阅以下配方了解完整概念:
-
第二章中的与秘密协作配方,逐步讲解 Kubernetes 概念
-
第二章中 与命名空间的协作 章节,走进 Kubernetes 概念
设置节点中的资源
计算资源管理在任何基础设施中都非常重要。我们应该充分了解我们的应用程序,并保留足够的 CPU 和内存容量,以避免资源耗尽。在本节中,我们将介绍如何管理 Kubernetes 节点中的节点容量。此外,我们还将描述如何管理 pod 的计算资源。
Kubernetes 有资源服务质量(QoS)的概念。它允许管理员优先分配资源给 pod。根据 pod 的设置,Kubernetes 将每个 pod 分类为以下之一:
-
Guaranteed pod
-
Burstable pod
-
BestEffort pod
优先级为 Guaranteed > Burstable > BestEffort。例如,如果一个 BestEffort pod 和一个 Guaranteed pod 存在于同一个 Kubernetes 节点上,且该节点遇到 CPU 问题或内存耗尽时,Kubernetes master 会首先终止 BestEffort pod。让我们来看看它是如何工作的。
准备工作
有两种方式可以设置资源 QoS:pod 配置或命名空间配置。如果您将资源 QoS 设置为命名空间,它将应用于所有属于该命名空间的 pod。如果您将资源 QoS 设置为 pod,它只会应用于该 pod。此外,如果同时设置了命名空间和 pod 的资源 QoS,系统会首先从命名空间配置中获取值,然后用 pod 配置覆盖。因此,我们将设置两个命名空间,一个配置了资源 QoS,另一个没有,以便观察它们的不同:
- 使用
kubectl命令创建两个命名空间,如下所示:
$ kubectl create namespace chap8-no-qos
namespace "chap8-no-qos" created
$ kubectl create namespace chap8-qos
namespace "chap8-qos" created
- 准备一个 YAML 文件,设置
spec.limits.defaultRequest.cpu: 0.1,如下所示:
$ cat resource-request-cpu.yml
apiVersion: v1
kind: LimitRange
metadata:
name: resource-request-cpu
spec:
limits:
- defaultRequest:
cpu: 0.1
type: Container
- 通过输入
kubectl命令,仅使其应用于chap8-qos命名空间:
$ kubectl create -f resource-request-cpu.yml --namespace=chap8-qos
limitrange "resource-request-cpu" created
- 使用
kubectl命令检查chap8-qos和chap8-no-qos上的资源限制:
//chap8-no-qos doesn't have any resource limits value
$ kubectl describe namespaces chap8-no-qos
Name: chap8-no-qos
Labels: <none>
Annotations: <none>
Status: Active
No resource quota.
No resource limits.
//chap8-qos namespace has a resource limits value
$ kubectl describe namespaces chap8-qos
Name: chap8-qos
Labels: <none>
Annotations: <none>
Status: Active
No resource quota.
Resource Limits
Type Resource Min Max Default Request Default Limit Max Limit/Request Ratio
---- -------- --- --- --------------- ------------- -----------------------
Container cpu - - 100m - -
如何操作...
让我们一步一步配置一个 BestEffort pod、一个 Guaranteed pod,然后一个 Burstable pod。
配置 BestEffort pod
BestEffort pod 在资源 QoS 类别中优先级最低。因此,在资源短缺的情况下,这个 BestEffort pod 会被 Kubernetes 调度器终止,并将 CPU 和内存资源让给其他更高优先级的 pod。
要将一个 pod 配置为 BestEffort,您需要将资源限制设置为 0(显式),或者不指定资源限制(隐式)。
- 准备一个 pod 配置,明确设置
spec.containers.resources.limits为0:
$ cat besteffort-explicit.yml
apiVersion: v1
kind: Pod
metadata:
name: besteffort
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
cpu: 0
memory: 0
- 在
chap8-qos和chap8-no-qos命名空间中创建 pod:
$ kubectl create -f besteffort-explicit.yml --namespace=chap8-qos
pod "besteffort" created
$ kubectl create -f besteffort-explicit.yml --namespace=chap8-no-qos
pod "besteffort" created
- 检查
QoS类别;两个 pod 都属于BestEffort类别:
$ kubectl describe pods besteffort --namespace=chap8-qos | grep QoS
QoS Class: BestEffort
$ kubectl describe pods besteffort --namespace=chap8-no-qos | grep QoS
QoS Class: BestEffort
有一个陷阱:如果你在 Pod 配置中没有设置任何资源设置,Pod 将从命名空间的默认设置中获取值。因此,如果你创建一个没有资源设置的 Pod,chap8-qos和chap8-no-qos之间的结果将会不同。以下示例演示了命名空间设置如何影响结果:
- 删除
chap8-qos和chap8-no-qos命名空间中的前置 Pod:
$ kubectl delete pod --all --namespace=chap8-qos
pod "besteffort" deleted
$ kubectl delete pod --all --namespace=chap8-no-qos
pod "besteffort" deleted
- 准备一个没有资源设置的 Pod 配置:
$ cat besteffort-implicit.yml
apiVersion: v1
kind: Pod
metadata:
name: besteffort
spec:
containers:
- name: nginx
image: nginx
- 在两个命名空间中创建 Pod:
$ kubectl create -f besteffort-implicit.yml --namespace=chap8-qos
pod "besteffort" created
$ kubectl create -f besteffort-implicit.yml --namespace=chap8-no-qos
pod "besteffort" created
QoS类的结果是不同的:
$ kubectl describe pods besteffort --namespace=chap8-no-qos |grep QoS
QoS Class: BestEffort
$ kubectl describe pods besteffort --namespace=chap8-qos |grep QoS
QoS Class: Burstable
因为chap8-qos命名空间的默认设置是request.cpu: 0.1,这导致 Pod 配置为Burstable类。因此,我们将使用chap8-no-qos命名空间,避免这种意外结果。
配置一个 Guaranteed Pod
Guaranteed 类具有资源QoS类中的最高优先级。在资源短缺的情况下,Kubernetes 调度器会尽量保留 Guaranteed Pod 直到最后。
为了将 Pod 配置为guaranteed类,明确设置资源限制和资源请求为相同值,或者只设置资源限制:
- 准备一个
resources.limit和resources.request具有相同值的 Pod 配置:
$ cat guaranteed.yml
apiVersion: v1
kind: Pod
metadata:
name: guaranteed-pod
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
cpu: 0.3
memory: 350Mi
requests:
cpu: 0.3
memory: 350Mi
- 在
chap8-no-qos命名空间中创建 Pod:
$ kubectl create -f guaranteed.yml --namespace=chap8-no-qos
pod "guaranteed-pod" created
- 检查
QoS 类;它有Guaranteed类:
$ kubectl describe pods guaranteed-pod --namespace=chap8-no-qos |grep QoS
QoS Class: Guaranteed
配置一个 Burstable Pod
Burstable Pod 的优先级高于 BestEffort,但低于 Guaranteed。为了将 Pod 配置为 Burstable Pod,你需要设置resources.request。resources.limit是可选的,但resources.request和resources.limit的值不能相等:
- 准备一个仅包含
resources.request的 Pod 配置:
$ cat burstable.yml
apiVersion: v1
kind: Pod
metadata:
name: burstable-pod
spec:
containers:
- name: nginx
image: nginx
resources:
requests:
cpu: 0.1
memory: 10Mi
limits:
cpu: 0.5
memory: 300Mi
- 创建 Pod:
$ kubectl create -f burstable.yml --namespace=chap8-no-qos
pod "burstable-pod" created
- 检查
QoS类;它是Burstable:
$ kubectl describe pods burstable-pod --namespace=chap8-no-qos |grep QoS
QoS Class: Burstable
它是如何工作的...
让我们看看资源请求/限制如何影响资源管理。一个前置的 burstable YAML 配置声明了不同阈值的请求和限制,如下所示:
| 资源定义类型 | 资源名称 | 值 | 描述 |
|---|---|---|---|
| requests | CPU | 0.1 | 至少占 1 个 CPU 核心的 10% |
| 内存 | 10Mi | 至少 10MB 内存 | |
| limits | CPU | 0.5 | 最大 50%的 1 个 CPU 核心 |
| 内存 | 300Mi | 最大 300MB 内存 |
对于 CPU 资源,接受的值表达式可以是核心数(0.1、0.2 … 1.0、2.0)或毫 CPU(100 m、200 m … 1000 m、2000 m)。1000 m 相当于 1.0 核心。例如,如果 Kubernetes 节点有 2 个 CPU 核心(或 1 个核心带超线程),则总共有 2.0 个核心或 2000 毫 CPU,如下图所示:

表示 2.0 CPU 资源
通过输入kubectl describe node <node name>,你可以查看节点上可用的资源:
//Find a node name
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
minikube Ready <none> 22h v1.9.0
//Specify node name 'minikube'
$ kubectl describe nodes minikube
Name: minikube
Roles: <none>
Labels: beta.kubernetes.io/arch=amd64
...
...
Allocatable:
cpu: 2 memory: 1945652Ki pods: 110
这显示了 minikube 节点,它有 2.0 CPU 和大约 1,945 MB 内存。如果你运行 nginx 示例(requests.cpu: 0.1),它至少占用 0.1 核,如下图所示:

请求 0.1 CPU 资源
只要 CPU 有足够的空间,它最多可以占用 0.5 核(limits.cpu: 0.5),如下图所示:

它最多可以占用 0.5 CPU 资源
因此,如果你将 requests.cpu 设置为超过 2.0,pod 将无法分配到此节点,因为可分配的 CPU 为 2.0,而 nginx pod 已经占用了至少 0.1 CPU。
另请参见
在本节中,你学习了如何通过设置资源请求和限制来配置资源 QoS。命名空间的默认值会影响最终的 pod 配置,因此你应该明确指定资源请求和限制。
请回顾以下章节,重温如何配置命名空间:
- 在第二章中,与命名空间一起工作,了解 Kubernetes 概念
使用 WebUI
Kubernetes 提供了一个 WebUI,用于可视化资源和机器的状态,也作为一个额外的界面来管理你的应用程序,无需命令行。在这个教程中,我们将介绍 Kubernetes 仪表板。
准备就绪
Kubernetes 仪表板 (github.com/kubernetes/dashboard) 类似于一个服务器端应用程序。开始时,只需确保你有一个健康的 Kubernetes 集群在运行,我们将在接下来的页面中介绍安装和相关设置。由于仪表板将通过浏览器访问,我们可以使用一个通过 minikube 启动、在笔记本电脑上运行的 Kubernetes 系统,并减少转发网络端口或设置防火墙规则的步骤。
对于通过 minikube 启动的 Kubernetes 系统,请检查 minikube 和系统本身是否正常工作:
// check if minikube runs well
$ minikube status
minikube: Running
cluster: Running
kubectl: Correctly Configured: pointing to minikube-vm at 192.168.99.100
// check the Kubernetes system by components
$ kubectl get cs
NAME STATUS MESSAGE ERROR
scheduler Healthy ok
controller-manager Healthy ok
etcd-0 Healthy {"health": "true"}
如何操作...
在使用 minikube 启动 Kubernetes 系统时,默认情况下会帮助创建仪表板。因此,我们将分别讨论这两种情况。
依赖 minikube 创建的仪表板
因为 Kubernetes 仪表板已经启动,我们要做的就是通过特定的 URL 打开 Web UI。这非常方便,你只需要在终端中执行一个命令:
$ minikube dashboard
Opening kubernetes dashboard in default browser...
然后,你将看到你喜欢的浏览器打开一个新网页,正如我们在第一章中介绍的,构建你自己的 Kubernetes 集群。其 URL 看起来像 MINIKUBE_VM_IP:30000/#!/overview?namespace=default。最重要的是,我们绕过了预期的网络代理和身份验证程序。
在使用其他启动工具的系统上手动创建仪表板
要运行 Kubernetes 仪表板,我们只需执行一个命令来应用配置文件,所有资源将自动创建:
$ kubectl create -f
https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml
secret "kubernetes-dashboard-certs" created
serviceaccount "kubernetes-dashboard" created
role "kubernetes-dashboard-minimal" created
rolebinding "kubernetes-dashboard-minimal" created
deployment "kubernetes-dashboard" created
service "kubernetes-dashboard" created
接下来,让我们使用命令 kubectl proxy 打开连接本地主机和 API 服务器的网关。然后,我们可以通过浏览器访问仪表板:
$ kubectl proxy
Starting to serve on 127.0.0.1:8001
一旦您看到如前所示的停止结果,您现在可以通过 URL 访问仪表板:localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/。在那里,您将在浏览器中看到以下屏幕:

Kubernetes 仪表板的登录门户
为了快速进入我们的演示,我们将使用现有服务帐户的令牌进行登录。无论您使用什么启动工具,使用仪表板创建的工具在任何情况下都是合适的:
// check the service account in your system
$ kubectl get secret -n kube-system
NAME TYPE DATA AGE
default-token-7jfmd kubernetes.io/service-account-token 3 51d
kubernetes-dashboard-certs Opaque 0 2d
kubernetes-dashboard-key-holder Opaque 2 51d
kubernetes-dashboard-token-jw42n kubernetes.io/service-account-token 3 2d
// grabbing token by checking the detail information of the service account with prefix "kubernetes-dashboard-token-"
$ kubectl describe secret kubernetes-dashboard-token-jw42n -n kube-system
Name: kubernetes-dashboard-token-jw42n
Namespace: kube-system
Labels: <none>
Annotations: kubernetes.io/service-account.name=kubernetes-dashboard
kubernetes.io/service-account.uid=253a1a8f-210b-11e8-b301-8230b6ac4959
Type: kubernetes.io/service-account-token
Data
====
ca.crt: 1066 bytes
namespace: 11 bytes
token:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Ii....
复制令牌并粘贴到浏览器控制台中,然后单击“SIGN IN”:

使用服务帐户的令牌进行身份验证
欢迎访问仪表板主页:

Kubernetes 仪表板的主页
工作原理...
Kubernetes 仪表板有两个主要功能:检查资源状态和部署资源。它可以覆盖我们在客户端终端上使用 kubectl 命令的大部分工作,但图形界面更加友好。
通过仪表板浏览您的资源
我们可以在仪表板上检查硬件和软件资源。例如,要查看集群中的节点,请在左侧菜单的集群部分下单击节点;当前集群中的每个节点将显示在页面上,并显示一些基本信息:

仪表板上的 Kubernetes 节点状态
由于基于您的环境,您在屏幕上看到的结果可能与前面的截图不同。继续单击一个节点的名称;将显示更多详细信息。其中一些以美观的图表形式呈现:

计算节点的资源状态
要显示软件资源,让我们看一下持有此仪表板的资源。在左侧菜单中,将命名空间更改为 kube-system,并单击概述,这将在单个页面上将所有资源汇集在一起,并使用清晰的图表显示出任何问题:

kube-system 命名空间的资源概述
还有更多内容;单击 kubernetes-dashboard 的部署,然后单击副本集中唯一 Pod 右侧的小文本文件图标。您可以查看容器的日志:

kubernetes-dashboard 的部署信息

仪表板应用程序的日志
现在,我们看到 Kubernetes 仪表盘提供了一个出色的界面,用于显示资源状态,涵盖节点、Kubernetes 工作负载和控制器,以及应用日志。
通过仪表盘部署资源
在这里,我们将准备一个 YAML 配置文件,用于在新的命名空间下创建 Kubernetes 部署和相关服务。它将通过仪表盘构建资源:
// the configuration file for creating Deployment and Service on new Namespace: dashboard-test
$ cat my-nginx.yaml
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: my-nginx
namespace: dashboard-test
spec:
replicas: 3
selector:
matchLabels:
run: demo
template:
metadata:
labels:
run: demo
spec:
containers:
- name: my-container
image: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: my-nginx
namespace: dashboard-test
spec:
ports:
- protocol: TCP
port: 80
type: NodePort
selector:
run: demo
首先,点击网页右上角的 CREATE 按钮。
部署有三种方法。我们选择第二种并上传先前介绍的配置文件。点击 UPLOAD 按钮:

通过配置文件创建资源
不幸的是,发生了错误:

由于部署问题,显示的错误信息
仪表盘根据给定的命名空间显示资源,该命名空间由用户在左侧菜单中选择。这个错误消息弹出并告诉用户,文件中提到的命名空间与仪表盘中的命名空间不匹配。我们需要做的是创建一个新命名空间并切换到它。
这次,我们将使用纯文本创建一个命名空间。再次点击 CREATE 按钮,选择从文本输入创建的方法。将以下内容粘贴到网页中,以创建一个新的命名空间:
apiVersion: v1
kind: Namespace
metadata:
name: dashboard-test
现在,我们有了一个新的命名空间,dashboard-test。在仪表盘中选择它作为主要命名空间,然后再次提交my-nginx.yaml文件:

提交配置文件之前,选择正确的命名空间
现在你可以看到该部署的概览!黄色圆圈表示待处理状态。一旦 pod 准备就绪,它们会变成绿色,如果失败,则变为红色。但是,如果你按照以下步骤进行操作,你将看不到红色标记:

创建资源的状态图
通过仪表盘删除资源
我们还可以通过仪表盘删除 Kubernetes 资源。尝试自己找到我们刚刚创建的服务my-nginx!执行以下操作:
-
将左侧菜单中的命名空间更改为 dashboard-test
-
点击左侧菜单中的服务部分,在“发现与负载均衡”下选择服务
-
点击超链接名称上的服务
my-nginx -
点击页面右上角的 DELETE 按钮,在 CREATE 按钮下方
就这样!当你看到屏幕上出现确认信息时,只需点击确认。最后,你不仅创建了一个资源,还从 Kubernetes 仪表盘中删除了它。
另见
本教程描述了如何启动一个 Web 界面,它将帮助你轻松地探索和管理 Kubernetes 实例,如 pod、部署和服务,而无需使用kubectl命令。有关如何通过kubectl命令获取详细信息的其他教程,请参见下方。
- 在《第二章》走进 Kubernetes 概念中,使用 Pods,Deployment API和使用 Services的示例
使用 RESTful API
用户可以通过kubectl命令控制 Kubernetes 集群;它支持本地和远程执行。但是,某些管理员或操作员可能需要集成程序来控制 Kubernetes 集群。
Kubernetes 具有 RESTful API 通过 API 控制 Kubernetes 集群,类似于kubectl命令。让我们学习如何通过提交 API 请求管理 Kubernetes 资源。
准备就绪
在此示例中,为了绕过额外的网络设置和验证权限的需求,我们将演示一个由minikube创建的集群,并使用 Kubernetes 代理:在主机上创建一个 Kubernetes 集群并启用本地 API 服务器的代理入口非常容易。
首先,启动一个代理以快速转发 API 请求:
//curl by API endpoint
$ kubectl proxy
Starting to serve on 127.0.0.1:8001
使用 Kubernetes 代理工作一段时间后,您可能会发现kubectl proxy命令会在终端上阻塞,强制您打开新的通道来执行后续命令,这有点让人恼火。为了避免这种情况,只需在您的命令中添加&作为最后一个参数。这个 shell 中的&符号将使您的命令在后台运行:
$ kubectl proxy &
[1] 6372
Starting to serve on 127.0.0.1:8001
请注意,如果不使用代理,则应手动终止此进程:
$ kill -j9 6372
然后,最好尝试简单路径/api的端点:
$ curl http://127.0.0.1:8001/api
{
"kind": "APIVersions",
"versions": [
"v1"
],
"serverAddressByClientCIDRs": [
{
"clientCIDR": "0.0.0.0/0",
"serverAddress": "10.0.2.15:8443"
}
]
}
一旦您看到像上面的基本 API 服务器信息显示的代码,恭喜!您现在可以与 Kubernetes 的 RESTful API 互动了。
安全访问 Kubernetes API 服务器的方法
但是,如果您考虑访问更安全的 API 服务器,例如 kubeadm 集群,则应注意以下事项:
-
API 服务器的端点
-
用于认证的令牌
我们可以通过以下命令获取所需信息。然后,您可以成功地发出版本的 API 请求:
$ APISERVER=$(kubectl config view | grep server | cut -f 2- -d ":" | tr -d " ")
// get the token of default service account
$ TOKEN=$(kubectl get secret --field-selector type=kubernetes.io/service-account-token -o name | grep default-token- | head -n 1 | xargs kubectl get -o 'jsonpath={.data.token}' | base64 -d)
$ curl $APISERVER/api -H "Authorization: Bearer $TOKEN" --insecure
另一方面,当访问 kubeadm 中的资源时,您可能会看到permission denied的消息。如果是这样,请将默认服务账户绑定到 kubeadm 系统中的管理员角色,即cluster-admin。我们在代码包中提供了配置文件rbac.yaml,如果需要,请查看:
$ curl $APISERVER/api/v1/namespaces/default/services -H "Authorization: Bearer $TOKEN" --insecure
...
"status": "Failure",
"message": "services is forbidden: User \"system:serviceaccount:default:default\" cannot list services in the namespace \"default\"",
"reason": "Forbidden",
...
$ kubectl create -f rbac.yaml
clusterrolebinding "fabric8-rbac" created
// now the API request is successful
$ curl $APISERVER/api/v1/namespaces/default/services -H "Authorization: Bearer $TOKEN" --insecure
{
"kind": "ServiceList",
"apiVersion": "v1",
"metadata": {
"selfLink": "/api/v1/namespaces/default/services",
"resourceVersion": "291954"
},
...
要小心使用--insecure标志,因为端点使用 HTTPS 协议,并且-H,添加带有令牌的标头。这些与我们的天真演示设置相比是额外的。
如何做...
在本节中,我们将向您展示如何通过 RESTful API 管理资源。通常情况下,curl命令行模式将涵盖以下想法:
-
操作:未指定操作的
curl默认将触发GET。要指定您的操作,请添加带有X标志的操作。 -
请求体数据:就像通过
kubectl创建 Kubernetes 资源一样,我们使用d标志来应用资源配置。带有@符号的值可以附加文件。此外,h标志有助于添加请求头;在这里我们需要以 JSON 格式添加内容类型。 -
URL:根据不同的功能,端点后面有各种路径。
让我们使用以下 JSON 配置文件创建一个部署:
$ cat nginx-deployment.json
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "my-nginx"
},
"spec": {
"replicas": 2,
"selector": {
"matchLabels": {
"app": "nginx"
}
},
"template": {
"metadata": {
"labels": {
"app": "nginx"
}
},
"spec": {
"containers": [
{
"image": "nginx",
"name": "my-nginx"
}
]
}
}
}
}
我们可以在 API 参考页面获取每个功能(kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/)。这类似于在编写配置文件时搜索资源的配置。要提交 API 请求,你应该知道要操作的资源类型,以及要执行的操作。按照以下步骤,在参考网页上找到相应的信息:
-
选择一个资源。
-
选择一个操作,例如,读取或写入。
-
选择操作的详细信息,例如,创建或删除。
-
信息将在网页的中间面板显示。一个可选步骤是将
kubectl切换为curl,位于控制台的右上角。更多细节,例如命令标志,将显示在右侧面板。
要检查创建部署的信息,你的网页控制台可能会像这张截图一样:

查找 API 路径以创建部署的步骤
根据参考页面,我们现在可以组合一个指定的curl命令并发出请求:
$ curl -X POST -H "Content-type: application/json" -d @nginx-deployment.json http://localhost:8001/apis/apps/v1/namespaces/default/deployments
{
"kind": "Deployment",
"apiVersion": "apps/v1",
"metadata": {
"name": "my-nginx",
"namespace": "default",
"selfLink": "/apis/apps/v1/namespaces/default/deployments/my-nginx",
"uid": "6eca324e-2cc8-11e8-806a-080027b04dc6",
"resourceVersion": "209",
"generation": 1,
"creationTimestamp": "2018-03-21T05:26:39Z",
"labels": {
"app": "nginx"
}
},
...
对于成功的请求,服务器返回资源的状态。继续检查我们是否能通过kubectl命令找到新的部署:
$ kubectl get deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
my-nginx 2 2 2 2 1m
当然,通过 RESTful API 检查时也可以正常工作:
// the operation "-X GET" can be ignored, since
$ curl -X GET http://localhost:8001/apis/apps/v1/namespaces/default/deployments
接下来,尝试删除这个新的部署my-nginx,这也是一种写操作:
$ curl -X DELETE http://localhost:8001/apis/apps/v1/namespaces/default/deployments/my-nginx
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {
},
"status": "Success",
"details": {
"name": "my-nginx",
"group": "apps",
"kind": "deployments",
"uid": "386a3aaa-2d2d-11e8-9843-080027b04dc6"
}
}
它是如何工作的...
RESTful API 允许进行 CRUD(创建、读取、更新和删除)操作,这些概念在每个现代 Web 应用程序中都是相同的。有关更多详细信息,请参阅en.wikipedia.org/wiki/Create,_read,_update_and_delete。
根据 CRUD 结构,Kubernetes 的 RESTful API 有以下基本方法:
| 操作 | HTTP 方法 | 示例 |
|---|---|---|
| 创建 | POST |
POST /api/v1/namespaces/default/pods |
| 读取 | GET |
GET /api/v1/componentstatuses |
| 更新 | PUT |
PUT /apis/apps/v1/namespaces/default/deployments/my-nginx |
| 删除 | DELETE |
DELETE /api/v1/namespaces/default/services/nginx-service |
正如我们在 第三章的配方 使用配置文件 中提到的,玩转容器,Kubernetes 使用 swagger (swagger.io/) 和 OpenAPI (www.openapis.org) 构建了 RESTful API。我们可以打开集群的 swagger UI 控制台来查看 API 功能。不过,建议通过我们在上一节演示的官方网站来查看它们,网站上的描述更加详细且用户友好。
还有更多内容...
利用 Kubernetes API 的一种更具程序化的方式是使用客户端库 (kubernetes.io/docs/reference/client-libraries/)。充分利用这些客户端工具不仅能节省资源管理的时间,还能打造一个强大而可靠的 CI/CD 环境。在这里,我们将介绍 Python 的 Kubernetes 客户端库:github.com/kubernetes-client/python。首先,你需要安装 Kubernetes 的 Python 库:
$ pip install kubernetes
然后,请将以下 Python 文件与 JSON 配置文件 nginx-deployment.json 放在同一位置,并确保执行 kubectl 时系统可以正常工作:
$ cat create_deployment.py
from kubernetes import client, config
import json
config.load_kube_config()
resource_config = json.load(open("./nginx-deployment.json"))
api_instance = client.AppsV1Api()
response = api_instance.create_namespaced_deployment(body=resource_config, namespace="default")
print("success, status={}".format(response.status))
现在你甚至不需要启用 Kubernetes 代理,直接运行这个脚本,看看会发生什么:
$ python create_deployment.py
另请参见
本配方描述了如何通过程序使用 Kubernetes RESTful API。将其远程集成到自动化程序中非常重要。有关详细的参数和安全性增强,请参考以下配方:
-
第三章中的 使用配置文件 配方,玩转容器
-
第七章中的 身份验证与授权 配方,在 GCP 上构建 Kubernetes
使用 Kubernetes DNS
当你将多个 pod 部署到 Kubernetes 集群时,服务发现是最重要的功能之一,因为 pod 可能依赖于其他 pod,但 pod 的 IP 地址在重启时会发生变化。你需要有一种灵活的方式将 pod 的 IP 地址传递给其他 pod。Kubernetes 提供了一个名为 kube-dns 的插件功能,能够帮助解决这个问题。它可以注册并查找 pod 和 Kubernetes 服务的 IP 地址。
在本节中,我们将探讨如何使用 kube-dns,它为你提供了在 Kubernetes 集群中配置 DNS 的灵活方式。
准备工作
自 Kubernetes 1.3 版本起,kube-dns 已经与 Kubernetes 一起发布,并默认启用。要检查 kube-dns 是否正常工作,可以使用以下命令检查 kube-system 命名空间:
$ kubectl get deploy kube-dns --namespace=kube-system
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
kube-dns 1 1 1 1 1d
如果你正在使用 minikube,请输入以下命令查看插件的状态:
$ minikube addons list |grep kube-dns
- kube-dns: enabled
如果显示为禁用状态,你需要使用以下命令启用它:
$ minikube addons enable kube-dns
此外,准备两个命名空间,chap8-domain1和chap8-domain2,以演示kube-dns如何分配域名:
$ kubectl create namespace chap8-domain1
namespace "chap8-domain1" created
$ kubectl create namespace chap8-domain2
namespace "chap8-domain2" created
//check chap8-domain1 and chap8-domain2
$ kubectl get namespaces
NAME STATUS AGE
chap8-domain1 Active 16s
chap8-domain2 Active 14s
default Active 4h
kube-public Active 4h
kube-system Active 4h
如何执行...
kube-dns为 Pods 和 Kubernetes 服务分配完全限定域名(FQDN)。我们来看一些差异。
Pod 的 DNS
Kubernetes 为 Pod 分配的域名格式为<IP 地址>.<命名空间名>.pod.cluster.local。由于它使用 Pod 的 IP 地址,FQDN 不保证始终存在,但如果应用程序需要 FQDN,这样做会很方便。
让我们在chap8-domain1和chap8-domain2上部署 apache2(httpd),如下所示:
$ kubectl run my-apache --image=httpd --namespace chap8-domain1
deployment "my-apache" created
$ kubectl run my-apache --image=httpd --namespace chap8-domain2
deployment "my-apache" created
输入kubectl get pod -o wide以捕获这些 Pods 的 IP 地址:
$ kubectl get pods -o wide --namespace=chap8-domain1
NAME READY STATUS RESTARTS AGE IP NODE
my-apache-55fb679f49-qw58f 1/1 Running 0 27s 172.17.0.4 minikube
$ kubectl get pods -o wide --namespace=chap8-domain2
NAME READY STATUS RESTARTS AGE IP NODE
my-apache-55fb679f49-z9gsr 1/1 Running 0 26s 172.17.0.5 minikube
这显示了chap8-domain1上的my-apache-55fb679f49-qw58f使用172.17.0.4。另一方面,chap8-domain2上的my-apache-55fb679f49-z9gsr使用172.17.0.5。
在这种情况下,FQDN 将是:
-
172-17-0-4.chap8-domain1.pod.cluster.local(chap8-domain1) -
172-17-0-5.chap8-domain2.pod.cluster.local(chap8-domain2)
请注意,IP 地址中的点(.)被改为连字符(-)。这是因为点是用来区分子域的分隔符。
为了检查名称解析是否有效,在前台启动 busybox pod(使用-it选项)。然后使用nslookup命令将 FQDN 解析为 IP 地址,具体步骤如下:
- 使用
-it选项运行busybox:
$ kubectl run -it busybox --restart=Never --image=busybox
- 在 busybox pod 中,输入
nslookup来解析chap8-domain1上 apache 的 FQDN:
# nslookup 172-17-0-4.chap8-domain1.pod.cluster.local
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: 172-17-0-4.chap8-domain1.pod.cluster.local
Address 1: 172.17.0.4
- 同样,输入
nslookup来解析chap8-domain2上 apache 的 FQDN:
# nslookup 172-17-0-5.chap8-domain2.pod.cluster.local
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: 172-17-0-5.chap8-domain2.pod.cluster.local
Address 1: 172.17.0.5
- 退出 busybox pod,然后删除它以释放资源:
# exit
$ kubectl delete pod busybox
pod "busybox" deleted
Kubernetes 服务的 DNS
首先,Kubernetes 服务的 DNS 在服务发现方面最为重要。因为应用程序通常连接到 Kubernetes 服务,而不是直接连接到 Pod。这就是为什么应用程序查询 Kubernetes 服务的 DNS 条目比查询 Pod 的 DNS 条目更频繁的原因。
其次,Kubernetes 服务的 DNS 条目将使用 Kubernetes 服务的名称,而不是 IP 地址。例如,它将像这样:<服务名称>.<命名空间名>.svc.cluster.local。
最后,Kubernetes 服务对 DNS 有两种不同的行为;即普通服务或无头服务。普通服务有自己的 IP 地址,而无头服务使用 Pod 的 IP 地址。我们先从普通服务开始。
普通服务是默认的 Kubernetes 服务。它将分配一个 IP 地址。按照以下步骤创建普通服务并检查 DNS 如何工作:
- 为
chap8-domain1和chap8-domain2上的 apache 创建普通服务:
$ kubectl expose deploy my-apache --namespace=chap8-domain1 --name=my-apache-svc --port=80 --type=ClusterIP
service "my-apache-svc" exposed
$ kubectl expose deploy my-apache --namespace=chap8-domain2 --name=my-apache-svc --port=80 --type=ClusterIP
service "my-apache-svc" exposed
- 通过运行以下命令检查这两个服务的 IP 地址:
$ kubectl get svc my-apache-svc --namespace=chap8-domain1
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-apache-svc ClusterIP 10.96.117.206 <none> 80/TCP 32s
$ kubectl get svc my-apache-svc --namespace=chap8-domain2
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-apache-svc ClusterIP 10.105.27.49 <none> 80/TCP 49s
- 为了执行名称解析,请在前台使用 busybox pod:
$ kubectl run -it busybox --restart=Never --image=busybox
- 在 busybox pod 中,使用
nslookup命令查询这两个服务的 IP 地址:
//query Normal Service on chap8-domain1
# nslookup my-apache-svc.chap8-domain1.svc.cluster.local
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: my-apache-svc.chap8-domain1.svc.cluster.local
Address 1: 10.96.117.206 my-apache-svc.chap8-domain1.svc.cluster.local
//query Normal Service on chap8-domain2
# nslookup my-apache-svc.chap8-domain2.svc.cluster.local
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: my-apache-svc.chap8-domain2.svc.cluster.local
Address 1: 10.105.27.49 my-apache-svc.chap8-domain2.svc.cluster.local
- 访问 apache 服务,检查流量是否可以分发到后端 apache pod:
# wget -q -O - my-apache-svc.chap8-domain1.svc.cluster.local
<html><body><h1>It works!</h1></body></html>
# wget -q -O - my-apache-svc.chap8-domain2.svc.cluster.local
<html><body><h1>It works!</h1></body></html>
- 退出
busyboxpod 并删除它:
# exit
$ kubectl delete pod busybox
pod "busybox" deleted
普通服务的 DNS 表现得像一个代理;流量会发送到普通服务,然后调度到 pod。那么无头服务呢?这一点将在如何工作...部分讨论。
StatefulSet 的 DNS
StatefulSet 在第三章《与容器一起玩》中已描述。它为 pod 分配带有序号的名称——例如,my-nginx-0,my-nginx-1,my-nginx-2。StatefulSet 还使用这些 pod 名称来分配 DNS 条目,而不是 IP 地址。因为它使用 Kubernetes 服务,完全合格域名(FQDN)如下所示:<StatefulSet 名称>-<序号>.<服务名称>.<命名空间名称>.svc.cluster.local。
让我们创建 StatefulSet,检查 StatefulSet 中的 DNS 是如何工作的:
- 准备 StatefulSet 和普通服务的 YAML 配置,如下所示:
$ cat nginx-sts.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-sts-svc
labels:
app: nginx-sts
spec:
ports:
- port: 80
selector:
app: nginx-sts
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: nginx-sts
spec:
serviceName: "nginx-sts-svc"
replicas: 3
template:
metadata:
labels:
app: nginx-sts
spec:
containers:
- name: nginx-sts
image: nginx
ports:
- containerPort: 80
restartPolicy: Always
- 在
chap8-domain2上创建 StatefulSet:
$ kubectl create -f nginx-sts.yaml --namespace=chap8-domain2
service "nginx-sts-svc" created
statefulset "nginx-sts" created
- 使用
kubectl命令检查 pod 和服务的创建状态:
//check StatefulSet (in short sts)
$ kubectl get sts --namespace=chap8-domain2
NAME DESIRED CURRENT AGE
nginx-sts 3 3 46s
//check Service (in short svc)
$ kubectl get svc nginx-sts-svc --namespace=chap8-domain2
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-sts-svc ClusterIP 10.104.63.124 <none> 80/TCP 8m
//check Pod with "-o wide" to show an IP address
$ kubectl get pods --namespace=chap8-domain2 -o wide
NAME READY STATUS RESTARTS AGE IP NODE
my-apache-55fb679f49-z9gsr 1/1 Running 1 22h 172.17.0.4 minikube
nginx-sts-0 1/1 Running 0 2m 172.17.0.2 minikube
nginx-sts-1 1/1 Running 0 2m 172.17.0.9 minikube
nginx-sts-2 1/1 Running 0 1m 172.17.0.10 minikube
- 在前台启动
busyboxpod:
$ kubectl run -it busybox --restart=Never --image=busybox
- 使用
nslookup命令查询服务的 IP 地址:
# nslookup nginx-sts-svc.chap8-domain2.svc.cluster.local
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: nginx-sts-svc.chap8-domain2.svc.cluster.local
Address 1: 10.104.63.124 nginx-sts-svc.chap8-domain2.svc.cluster.local
- 使用
nslookup命令查询单个 pod 的 IP 地址:
# nslookup nginx-sts-0.nginx-sts-svc.chap8-domain2.svc.cluster.local
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: nginx-sts-0.nginx-sts-svc.chap8-domain2.svc.cluster.local
Address 1: 172.17.0.2 nginx-sts-0.nginx-sts-svc.chap8-domain2.svc.cluster.local
# nslookup nginx-sts-1.nginx-sts-svc.chap8-domain2.svc.cluster.local
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: nginx-sts-1.nginx-sts-svc.chap8-domain2.svc.cluster.local
Address 1: 172.17.0.9 nginx-sts-1.nginx-sts-svc.chap8-domain2.svc.cluster.local
# nslookup nginx-sts-2.nginx-sts-svc.chap8-domain2.svc.cluster.local
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: nginx-sts-2.nginx-sts-svc.chap8-domain2.svc.cluster.local
Address 1: 172.17.0.10 nginx-sts-2.nginx-sts-svc.chap8-domain2.svc.cluster.local
- 清理
busyboxpod:
# exit
$ kubectl delete pod busybox
pod "busybox" deleted
如何工作...
我们已经设置了几个组件来查看 DNS 条目是如何最初创建的。Kubernetes 服务名称在确定 DNS 名称时尤其重要。
然而,Kubernetes 服务有两种模式,要么是普通服务,要么是无头服务。普通服务在前面部分已经描述过;它有自己的 IP 地址。另一方面,无头服务没有 IP 地址。
让我们看看如何创建无头服务,以及名称解析是如何工作的:
- 为
chap8-domain1和chap8-domain2上的 apache 创建无头服务(指定--cluster-ip=None):
$ kubectl expose deploy my-apache --namespace=chap8-domain1 --name=my-apache-svc-hl --port=80 --type=ClusterIP --cluster-ip=None
service "my-apache-svc-hl" exposed
$ kubectl expose deploy my-apache --namespace=chap8-domain2 --name=my-apache-svc-hl --port=80 --type=ClusterIP --cluster-ip=None
service "my-apache-svc-hl" exposed
- 使用以下命令检查这两个无头服务没有 IP 地址:
$ kubectl get svc my-apache-svc-hl --namespace=chap8-domain1
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-apache-svc-hl ClusterIP None <none> 80/TCP 13m
$ kubectl get svc my-apache-svc-hl --namespace=chap8-domain2
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-apache-svc-hl ClusterIP None <none> 80/TCP 13m
- 在前台启动
busyboxpod:
$ kubectl run -it busybox --restart=Never --image=busybox
- 在
busyboxpod 中查询这两个服务。它必须显示作为 pod 地址的地址(172.168.0.4和172.168.0.5):
# nslookup my-apache-svc-hl.chap8-domain1.svc.cluster.local
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: my-apache-svc-hl.chap8-domain1.svc.cluster.local
Address 1: 172.17.0.4 # nslookup my-apache-svc-hl.chap8-domain2.svc.cluster.local
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: my-apache-svc-hl.chap8-domain2.svc.cluster.local
Address 1: 172.17.0.5
- 退出
busyboxpod 并删除它:
# exit
$ kubectl delete pod busybox
pod "busybox" deleted
当 pod 扩展时的无头服务
上面的示例只显示了一个 IP 地址,因为我们只设置了一个 Pod。如果使用kubectl scale命令增加一个实例,会发生什么?
让我们将chap8-domain1上的 Apache 实例从 1 个增加到 3 个,然后查看无头服务 DNS 是如何工作的:
//specify --replicas=3
$ kubectl scale deploy my-apache --namespace=chap8-domain1 --replicas=3
deployment "my-apache" scaled
//Now there are 3 Apache Pods
$ kubectl get pods --namespace=chap8-domain1 -o wide
NAME READY STATUS RESTARTS AGE IP NODE
my-apache-55fb679f49-c8wg7 1/1 Running 0 1m 172.17.0.7 minikube
my-apache-55fb679f49-cgnj8 1/1 Running 0 1m 172.17.0.8 minikube
my-apache-55fb679f49-qw58f 1/1 Running 0 8h 172.17.0.4 minikube
//launch busybox to run nslookup command
$ kubectl run -it busybox --restart=Never --image=busybox
//query Headless service name
# nslookup my-apache-svc-hl.chap8-domain1.svc.cluster.local
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: my-apache-svc-hl.chap8-domain1.svc.cluster.local
Address 1: 172.17.0.4
Address 2: 172.17.0.7
Address 3: 172.17.0.8
//quit busybox and release it
# exit
$ kubectl delete pod busybox
pod "busybox" deleted
结果很简单:一个 DNS 条目,my-apache-svc-hl.chap8-domain1.svc.cluster.local 返回了 3 个 IP 地址。因此,当你的 HTTP 客户端尝试访问 Kubernetes 服务 my-apache-svc-hl.chap8-domain1.svc.cluster.local 时,它会从kube-dns获取这 3 个 IP 地址,然后直接访问其中一个,如下图所示:

访问无头服务和 pod 的顺序
因此,Kubernetes 无头服务并不进行任何流量调度,这就是它被称为无头的原因。
另见
本节描述了kube-dns如何在 DNS 中为 Pods 和服务命名。理解普通服务和无头服务之间的区别对于理解如何连接到应用程序非常重要。以下食谱中还描述了 StatefulSet 的使用场景:
- 在第三章《与容器玩耍》中,确保容器的灵活使用
认证与授权
认证与授权对于像 Kubernetes 这样的平台至关重要。认证确保用户的身份是他们所声称的,而授权则验证用户是否拥有执行特定操作的足够权限。Kubernetes 支持各种认证和授权插件。
准备工作
当请求到达 API 服务器时,它首先通过验证客户端证书与 API 服务器中的证书颁发机构(CA)建立 TLS 连接。API 服务器中的 CA 通常位于/etc/kubernetes/,而客户端证书通常位于$HOME/.kube/config。握手完成后,进入认证阶段。在 Kubernetes 中,认证模块是链式的。我们可以使用多个认证模块。当请求到来时,Kubernetes 会逐个尝试所有认证器,直到成功为止。如果所有认证模块都失败,请求将被拒绝并返回 HTTP 401 未授权。如果认证器中的某一个验证了用户身份,则请求通过认证。接下来,Kubernetes 的授权模块开始工作。它们通过一套策略验证用户是否具有执行请求的权限。授权模块会逐个检查。与认证模块类似,如果所有模块都失败,请求将被拒绝。如果用户有资格发起请求,请求将通过认证和授权模块,并进入准入控制模块。请求将依次通过各个准入控制器的检查。如果任何准入控制器失败,请求将立即被拒绝。
下图演示了这一顺序:

请求通过 Kubernetes API 服务器
如何操作...
在 Kubernetes 中,有两种类型的账户:服务账户和用户账户。它们之间的主要区别在于,用户账户不在 Kubernetes 中存储和管理,且不能通过 API 调用添加。以下表格是一个简单的对比:
| 服务账户 | 用户账户 | |
|---|---|---|
| 范围 | 命名空间 | 全局 |
| 使用者 | 进程 | 普通用户 |
| 创建者 | API 服务器或通过 API 调用 | 管理员,无法通过 API 调用添加 |
| 由谁管理 | API 服务器 | 集群外部 |
服务账户用于 Pod 内部的进程与 API 服务器进行通信。Kubernetes 默认会创建一个名为default的服务账户。如果 Pod 没有关联服务账户,它将被分配给默认的服务账户:
// check default service accoun
# kubectl describe serviceaccount default
Name: default
Namespace: default
Labels: <none>
Annotations: <none>
Image pull secrets: <none>
Mountable secrets: default-token-q4qdh
Tokens: default-token-q4qdh
Events: <none>
我们可能会发现与该服务账户相关联的一个密钥。这个密钥由令牌控制器管理。当创建一个新的服务账户时,控制器将创建一个令牌并将其与服务账户通过kubernetes.io/service-account.name注解关联,从而允许 API 访问。在 Kubernetes 中,令牌的格式是密钥格式。任何具有密钥查看权限的人都可以查看该令牌。以下是创建服务账户的示例:
// configuration file of a ServiceAccount named chapter8-serviceaccount
# cat serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: chapter8-serviceaccount
// create service account
# kubectl create -f serviceaccount.yaml
serviceaccount "chapter8-serviceaccount" created
// describe the service account we just created
# kubectl describe serviceaccount chapter8-serviceaccount
Name: chapter8-serviceaccount
Namespace: default
Labels: <none>
Annotations: <none>
Image pull secrets: <none>
Mountable secrets: chapter8-serviceaccount-token-nxh47
Tokens: chapter8-serviceaccount-token-nxh47
Events: <none>
身份验证
Kubernetes 支持几种账户身份验证策略,从客户端证书、承载令牌、静态文件到 OpenID 连接令牌。可以选择多个选项并与其他选项结合使用,形成身份验证链。在本食谱中,我们将介绍如何使用令牌、客户端证书和 OpenID 连接令牌身份验证器。
服务账户令牌身份验证
我们在上一节中已经创建了一个服务账户,现在让我们看看如何使用服务账户令牌进行身份验证。我们需要先获取令牌:
// check the details of the secret
# kubectl get secret chapter8-serviceaccount-token-nxh47 -o yaml
apiVersion: v1
data:
ca.crt: <base64 encoded>
namespace: ZGVmYXVsdA==
token: <bearer token, base64 encoded>
kind: Secret
metadata:
annotations:
kubernetes.io/service-account.name: chapter8-serviceaccount
name: chapter8-serviceaccount-token-nxh47
namespace: default
...
type: kubernetes.io/service-account-token
我们可以看到数据下的三个项目都是 base64 编码的。我们可以通过 Linux 中的echo "encoded content" | base64 --decode命令轻松解码它们。例如,我们可以解码编码的命名空间内容:
# echo "ZGVmYXVsdA==" | base64 --decode
default
使用相同的命令,我们可以获取承载令牌,并将其用于请求。API 服务器期望请求中带有 HTTP 头Authorization: Bearer $TOKEN。以下是如何使用令牌进行身份验证并直接向 API 服务器发起请求的示例。
首先,我们需要获取我们解码后的令牌:
// get the decoded token from secret chapter8-serviceaccount-token-nxh47
# TOKEN=`echo "<bearer token, base64 encoded>" | base64 --decode`
其次,我们还需要解码ca.crt:
// get the decoded ca.crt from secret chapter8-serviceaccount-token-nxh47
# echo "<ca.crt, base64 encoded>" | base64 --decode > cert
接下来,我们需要知道 API 服务器的地址。通过kubectl config view命令,我们可以获得服务器列表:
# kubectl config view
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: REDACTED
server: https://api.demo-k8s.net
name: demo-k8s.net
- cluster:
certificate-authority: /Users/chloelee/.minikube/ca.crt
server: https://192.168.99.100:8443
name: minikube
...
找到你当前使用的那个。在这个例子中,我们使用的是 minikube。服务器地址是https://192.168.99.100:8443。
你可以使用kubectl config current-context命令来查找当前的上下文。
然后我们就可以开始操作了!我们将通过https://$APISERVER/api直接请求 API 端点,并使用--cacert和--header选项。
# curl --cacert cert https://192.168.99.100:8443/api --header "Authorization: Bearer $TOKEN"
{
"kind": "APIVersions",
"versions": [
"v1"
],
"serverAddressByClientCIDRs": [
{
"clientCIDR": "0.0.0.0/0",
"serverAddress": "10.0.2.15:8443"
}
]
}
我们可以看到可用的版本是v1。让我们看看/api/v1端点里有什么:
# curl --cacert cert https://192.168.99.100:8443/api/v1 --header "Authorization: Bearer $TOKEN"
{
"kind": "APIResourceList",
"groupVersion": "v1",
"resources": [
...
{
"name": "configmaps",
"singularName": "",
"namespaced": true,
"kind": "ConfigMap",
"verbs": [
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
],
"shortNames": ["cm"]
}
], ...
}
它将列出我们请求的所有端点和动词。以configmaps为例,并使用grep命令过滤名称:
# curl --cacert cert https://192.168.99.100:8443/api/v1/configmaps --header "Authorization: Bearer $TOKEN" |grep \"name\"
"name": "extension-apiserver-authentication",
"name": "ingress-controller-leader-nginx",
"name": "kube-dns",
"name": "nginx-load-balancer-conf",
在我的集群中列出了四个默认的 configmaps。我们可以使用kubectl来验证这一点。结果应该与我们之前获得的结果一致:
# kubectl get configmaps --all-namespaces
NAMESPACE NAME DATA AGE
kube-system extension-apiserver-authentication 6 6d
kube-system ingress-controller-leader-nginx 0 6d
kube-system kube-dns 0 6d
kube-system nginx-load-balancer-conf 1 6d
X509 客户端证书
用户账户的一种常见身份验证策略是使用客户端证书。在以下示例中,我们将创建一个名为 Linda 的用户,并为她生成客户端证书:
// generate a private key for Linda
# openssl genrsa -out linda.key 2048
Generating RSA private key, 2048 bit long modulus
..............+++
..............+++
e is 65537 (0x10001)
// generate a certificate sign request (.csr) for Linda. Make sure /CN is equal to the username.
# openssl req -new -key linda.key -out linda.csr -subj "/CN=linda"
接下来,我们将通过私钥和签名请求文件生成一个证书,并包括我们的集群的 CA 和私钥:
在 minikube 中,它位于 ~/.minikube/。对于其他自托管解决方案,通常位于 /etc/kubernetes/ 下。如果使用 kops 部署集群,则路径位于 /srv/kubernetes,您可以在 /etc/kubernetes/manifests/kube-apiserver.manifest 文件中找到该路径。
// generate a cert
# openssl x509 -req -in linda.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out linda.crt -days 30
Signature ok
subject=/CN=linda
Getting CA Private Key
我们通过集群证书签署了 Linda,现在可以将其设置到 kubeconfig 文件中:
# kubectl config set-credentials linda --client-certificate=linda.crt --client-key=linda.key
User "linda" set.
我们可以使用 kubectl config view 来验证用户是否已设置:
# kubectl config view
current-context: minikube
kind: Config
users:
- name: linda
user:
client-certificate: /k8s-cookbooks-2e/ch8/linda.crt
client-key: /k8s-cookbooks-2e/ch8/linda.key
...
用户创建后,我们可以创建一个上下文,将命名空间和集群与该用户关联:
# kubectl config set-context linda-context --cluster=minikube --user=linda
之后,Kubernetes 应该能够识别 Linda,并将其传递到授权阶段。
OpenID Connect 令牌
另一种流行的身份验证策略是 OpenID Connect 令牌。将身份验证委托给 OAuth2 提供者,是一种方便的用户管理方式。要启用此功能,必须为 API 服务器设置两个必需的标志:--oidc-issuer-url,该标志表示发行者 URL,允许 API 服务器发现公共签名密钥;以及 --oidc-client-id,它是您的应用的客户端 ID,用于与您的发行者关联。有关完整信息,请参阅官方文档 kubernetes.io/docs/admin/authentication/#configuring-the-api-server。以下是我们在 minikube 集群中设置 Google OpenID 身份验证的示例。以下步骤可以轻松编程实现身份验证。
首先,我们需要向 Google 请求一个包含客户端 ID、客户端密钥和重定向 URL 的集合。以下是从 Google 请求和下载密钥的步骤:
-
在 GCP 控制台中,转到 APIs & Services | Credentials | Create credentials | OAuth client ID。
-
在应用类型中选择 Other 并点击 Create。
-
下载 JSON 文件。
之后,凭证成功创建。我们可以查看 JSON 文件。以下是我们从示例项目 kubernetes-cookbook 中得到的文件:
# cat client_secret_140285873781-f9h7d7bmi6ec1qa0892mk52t3o874j5d.apps.googleusercontent.com.json
{
"installed":{
"client_id":"140285873781
f9h7d7bmi6ec1qa0892mk52t3o874j5d.apps.googleusercontent.com",
"project_id":"kubernetes-cookbook",
"auth_uri":"https://accounts.google.com/o/oauth2/auth",
"token_uri":"https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs",
"client_secret":"Ez0m1L7436mlJQErhalp3Gda",
"redirect_uris":[
"urn:ietf:wg:oauth:2.0:oob",
"http://localhost"
]
}
}
现在,我们应该能够启动我们的集群。别忘了,OIDC 标志必须传递。 在 minikube 中,可以通过 --extra-config 参数来完成:
// start minikube cluster and passing oidc parameters.
# minikube start --extra-config=apiserver.Authorization.Mode=RBAC --extra-config=apiserver.Authentication.OIDC.IssuerURL=https://accounts.google.com --extra-config=apiserver.Authentication.OIDC.UsernameClaim=email --extra-config=apiserver.Authentication.OIDC.ClientID="140285873781-f9h7d7bmi6ec1qa0892mk52t3o874j5d.apps.googleusercontent.com"
集群启动后,用户必须登录到身份提供者以获取 access_token, id_token, 和 refresh_token。在 Google 中,您登录后会得到一个代码,然后通过请求将代码传递以获取令牌。然后,我们通过 kubectl 将令牌传递给 API 服务器的请求。以下是该过程的时序图:

Google OpenID Connect 身份验证的时间图
要请求代码,您的应用应发送如下格式的 HTTP 请求:
// https://accounts.google.com/o/oauth2/v2/auth?client_id=<client_id>&response_type=code&scope=openid%20email&redirect_uri=urn:ietf:wg:oauth:2.0:oob
# https://accounts.google.com/o/oauth2/v2/auth?client_id=140285873781-f9h7d7bmi6ec1qa0892mk52t3o874j5d.apps.googleusercontent.com&response_type=code&scope=openid%20email&redirect_uri=urn:ietf:wg:oauth:2.0:oob
然后,浏览器窗口会弹出,要求您登录 Google。登录后,代码将显示在控制台中:

接下来,我们将请求令牌的代码传递到 https://www.googleapis.com/oauth2/v4/token。然后,我们应该能够从响应中获取 access_token、refresh_token 和 id_token:
// curl -d "grant_type=authorization_code&client_id=<client_id>&client_secret=<client_secret>&redirect_uri=urn:ietf:wg:oauth:2.0:oob&code=<code>" -X POST https://www.googleapis.com/oauth2/v4/token
# curl -d "grant_type=authorization_code&client_id=140285873781-f9h7d7bmi6ec1qa0892mk52t3o874j5d.apps.googleusercontent.com&client_secret=Ez0m1L7436mlJQErhalp3Gda&redirect_uri=urn:ietf:wg:oauth:2.0:oob&code=4/AAAd5nqWFkpKmxo0b_HZGlcAh57zbJzggKmoOG0BH9gJhfgvQK0iu9w" -X POST https://www.googleapis.com/oauth2/v4/token
{
"access_token": "ya29.GluJBQIhJy34vqJl7V6lPF9YSXmKauvvctjUJHwx72gKDDJikiKzQed9iUnmqEv8gLYg43H6zTSYn1qohkNce1Q3fMl6wbrGMCuXfRlipTcPtZnFt1jNalqMMTCm",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "1/72xFflvdTRdqhjn70Bcar3qyWDiFw-8KoNm6LdFPorQ",
"id_token": "eyJhbGc...mapQ"
}
假设我们将使用用户 chloe-k8scookbook@gmail.com 来关联该 Google 账户。让我们在集群中创建它。我们可以将用户信息追加到我们的 kubeconfig 文件中。文件的默认位置是 $HOME/.kube/config:
// append to kubeconfig file.
- name: chloe-k8scookbook@gmail.com
user:
auth-provider:
config:
client-id: 140285873781-f9h7d7bmi6ec1qa0892mk52t3o874j5d.apps.googleusercontent.com
client-secret: Ez0m1L7436mlJQErhalp3Gda
id-token: eyJhbGc...mapQ
idp-issuer-url: https://accounts.google.com
refresh-token: 1/72xFflvdTRdqhjn70Bcar3qyWDiFw-8KoNm6LdFPorQ
name: oidc
之后,让我们使用该用户列出节点并查看是否可以通过身份验证:
# kubectl --user=chloe-k8scookbook@gmail.com get nodes
Error from server (Forbidden): nodes is forbidden: User "chloe-k8scookbook@gmail.com" cannot list nodes at the cluster scope
我们遇到了授权错误!在验证身份后,下一步将检查用户是否有足够的权限执行该请求。
授权
在通过身份验证阶段后,进行授权操作。我们在进入授权策略之前,先讨论一下 Role 和 RoleBinding。
Role 和 RoleBinding
Kubernetes 中的 Role 包含一组规则。规则通过指定 apiGroups、resources 和 verbs 来定义特定操作和资源的权限。例如,以下角色为 configmaps 定义了一个只读规则:
# cat role.yaml
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: configmap-ro
rules:
- apiGroups: ["*"]
resources: ["configmaps"]
verbs: ["watch", "get", "list"]
RoleBinding 用于将角色与一组账户关联。以下示例展示了我们将 configmap-ro 角色分配给一组主体,在此案例中只有用户 linda:
# cat rolebinding.yaml
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: devops-role-binding
subjects:
- apiGroup: ""
kind: User
name: linda
roleRef:
apiGroup: ""
kind: Role
name: configmap-ro
Role 和 RoleBinding 是命名空间级的。它们的作用范围仅限于单个命名空间。要访问集群范围的资源,我们需要使用 ClusterRole 和 ClusterRoleBinding。
要将命名空间添加到 Role 或 RoleBinding,只需在配置文件中的元数据部分添加命名空间字段。
ClusterRole 和 ClusterRoleBinding
ClusterRole 和 ClusterRoleBinding 基本上与 Role 和 RoleBinding 类似。与 Role 和 RoleBinding 仅限于单个命名空间的作用域不同,ClusterRole 和 ClusterRoleBinding 用于授予集群范围的资源。因此,ClusterRole 可以授予对所有命名空间的资源、非命名空间资源和非资源端点的访问权限,我们可以使用 ClusterRoleBinding 将用户与角色绑定。
我们还可以将服务账户与 ClusterRole 绑定。由于服务账户是命名空间级的,我们必须指定其完整名称,包括它所在的命名空间:
system:serviceaccount:<namespace>:<serviceaccountname>
以下是 ClusterRole 和 ClusterRoleBinding 的示例。在此角色中,我们为多个资源(如 deployments、replicasets、ingresses、pods 和 services)授予所有操作权限,并将命名空间和事件的权限限制为只读:
# cat serviceaccount_clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cd-role
rules:
- apiGroups: ["extensions", "apps"]
resources:
- deployments
- replicasets
- ingresses
verbs: ["*"]
- apiGroups: [""]
resources:
- namespaces
- events
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources:
- pods
- services
- secrets
- replicationcontrollers
- persistentvolumeclaims
- jobs
- cronjobs
verbs: ["*"]---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: cd-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cd-role
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: system:serviceaccount:default:chapter8-serviceaccount
注意 apiGroup 中的 [""];这表示 Kubernetes 中的核心组。要查看完整的资源和动词列表,请查看 Kubernetes API 参考网站:kubernetes.io/docs/reference/。
在这种情况下,我们创建了一个 cd-role,这是用于执行持续部署的角色。同时,我们创建了一个 ClusterRoleBinding,将服务账号 chapter8-serviceaccount 与 cd-role 关联起来。
基于角色的访问控制(RBAC)
基于角色的访问控制的概念涉及到 Role、ClusterRole、RoleBinding 和 ClusterRoleBinding。通过之前展示的 role.yaml 和 rolebinding.yaml 文件,Linda 应该获得对 configmaps 资源的只读访问权限。为了将授权规则应用于 chloe-k8scookbook@gmail.com,只需将 ClusterRole 和 ClusterRoleBinding 与其关联即可:
# cat oidc_clusterrole.yaml
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: oidc-admin-role
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: admin-binding
subjects:
- kind: User
name: chloe-k8scookbook@gmail.com
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: oidc-admin-role
apiGroup: rbac.authorization.k8s.io
然后,我们应该能够看到是否可以使用 chloe-k8scookbook@gmail.com 用户获取节点:
# kubectl --user=chloe-k8scookbook@gmail.com get nodes
NAME STATUS ROLES AGE VERSION minikube Ready <none> 6d v1.9.4
它像魔法一样起作用。我们再也没有遇到“Forbidden”错误。
在 RBAC 之前,Kubernetes 提供了 基于属性的访问控制 (ABAC),它允许集群管理员通过将一组用户授权策略定义为文件格式,每行一个 JSON。但该文件必须在启动 API 服务器时存在,这使得它在实际操作中不可用。自从 Kubernetes 1.6 引入了 RBAC 后,ABAC 成为遗留技术,并被弃用。
准入控制
准入控制模块在 Kubernetes 验证请求者身份并检查请求者是否具有足够权限执行操作后发挥作用。与身份验证和授权不同,准入控制可以查看请求的内容,甚至具备验证或修改请求的能力。如果请求未通过某个准入控制器,系统将立即拒绝该请求。要在 Kubernetes 中启用准入控制器,只需在启动 API 服务器时传递 --admission-control (version < 1.10) 或 --enable-admission-plugins (version >= 1.10) 参数。
根据您如何配置集群,传递 --enable-admission-plugin 参数的方法可能有所不同。在 minikube 中,添加 --extra-config=apiserver.Admission.PluginNames= $ADMISSION_CONTROLLERS 并用逗号分隔控制器,应该就能解决问题。
不同的准入控制器有不同的用途。在以下的配方中,我们将介绍一些重要的准入控制器以及 Kubernetes 官方推荐的用户应当使用的准入控制器。版本 >= 1.6.0 的推荐列表如下:NamespaceLifecycle、LimitRanger、ServiceAccount、PersistentVolumeLabel、DefaultStorageClass、DefaultTolerationSeconds、ResourceQuota。
请注意,准入控制器的顺序很重要,因为请求会按顺序依次通过(在 1.10 之前的版本中,使用 --admission-control 选项;在 v1.10 中,参数被替换为 --enable-admission-plugins,顺序不再重要)。我们不希望先进行 ResourceQuota 检查,然后在检查完长链条的准入控制器后发现资源信息已过时。
如果版本大于或等于 1.9.0,MutatingAdmissionWebhook和ValidatingAdmissionWebhook将在ResourceQuota之前添加。如需了解更多关于MutatingAdmissionWebhook和ValidatingAdmissionWebhook的信息,请参见本食谱中的更多内容部分。
NamespaceLifecycle
当命名空间被删除时,该命名空间中的所有对象也会被驱逐。此插件确保在终止或不存在的命名空间中无法创建新的对象请求。它还可以防止 Kubernetes 本地命名空间被删除。
LimitRanger
该插件确保LimitRange能够正常工作。通过LimitRange,我们可以在命名空间中设置默认的请求和限制,在启动 pod 时,如果没有指定请求和限制,将使用这些默认值。
ServiceAccount
如果打算在用例中使用 ServiceAccount 对象,则必须添加 ServiceAccount 插件。有关 ServiceAccount 的更多信息,请回顾本食谱中关于 ServiceAccount 的部分。
PersistentVolumeLabel(自 v1.8 起已弃用)
PersistentVolumeLabel为新创建的 PV 添加标签,这些标签基于底层云服务商提供的标签。从 1.8 版本起,该准入控制器已被弃用。此控制器的功能现在由云控制器管理器处理,该管理器定义了特定于云的控制逻辑并以守护进程方式运行。
DefaultStorageClass
该插件确保默认存储类能够按预期工作,如果在PersistentVolumeClaim中未设置StorageClass。不同的云服务商使用不同的配置工具将利用DefaultStorageClass(例如,GKE 使用 Google Cloud Persistent Disk)。确保已启用此功能。
DefaultTolerationSeconds
污点和容忍度用于防止一组 pod 在某些节点上调度运行。污点应用于节点,而容忍度则为 pod 指定。污点的值可以是NoSchedule或NoExecute。如果运行在有污点的节点上的 pod 没有匹配的容忍度,则该 pod 将被驱逐。
DefaultTolerationSeconds插件用于为没有设置容忍度的 pod 进行设置。然后,它将为notready:NoExecute和unreachable:NoExecute的污点应用默认容忍度,持续 300 秒。如果节点不可用或无法访问,则在 pod 从节点驱逐之前,等待 300 秒。
ResourceQuota
就像LimitRange一样,如果你使用ResourceQuota对象来管理不同级别的 QoS,必须启用此插件。ResourceQuota应始终放在准入控制插件列表的末尾。正如我们在ResourceQuota部分中提到的,如果使用的配额小于硬配额,则资源配额使用情况将被更新,以确保集群有足够的资源来接受请求。将其放在 ServiceAccount 准入控制器列表的末尾可以防止请求在最终被以下控制器拒绝之前提前增加配额使用量。
DenyEscalatingExec
此插件拒绝任何 kubectl exec 和 kubectl attach 命令的提升权限模式。具有提升权限模式的 Pods 可以访问宿主机命名空间,这可能会成为安全风险。
AlwaysPullImages
拉取策略定义了 kubelet 拉取镜像时的行为。默认的拉取策略是 IfNotPresent;也就是说,当镜像在本地不存在时,它将拉取镜像。如果启用此插件,默认的拉取策略将变为 Always,即始终拉取最新的镜像。如果你的集群由不同团队共享,此插件还提供了另一个好处。每当一个 pod 被调度时,无论镜像是否存在于本地,它都会始终拉取最新的镜像。这样,我们就可以确保 pod 创建请求始终经过镜像的授权检查。
要查看完整的准入控制器列表,请访问官方站点 (kubernetes.io/docs/admin/admission-controllers) 获取更多信息。
还有更多内容…
在 Kubernetes 1.7 之前,准入控制器需要与 API 服务器一起编译,并在 API 服务器启动之前进行配置。动态准入控制旨在突破这些限制。由于动态准入控制中的两个主要组件在我们编写这本书时尚未 GA,除了将它们添加到准入控制链中外,还需要在 API 服务器中进行额外的运行时配置:--runtime-config=admissionregistration.k8s.io/v1alpha1。
在 minikube 中,ServiceAccount 运行时配置设置为 api/all,因此默认启用。
初始化器(alpha)
初始化器是在对象初始化阶段的一组任务。它们可以是一组检查或变更,用于执行强制策略或注入默认值。例如,你可以实现一个初始化器,将一个 sidecar 容器或一个包含测试数据的卷注入到 pod 中。初始化器在对象的 metadata.initializers.pending 中配置。在对应的初始化器控制器(通过名称识别)完成任务后,它会将其名称从元数据中移除。如果由于某些原因某个初始化器工作不正常,所有使用该初始化器的对象将停留在 ServiceAccount 未初始化阶段,且在 API 中不可见。使用时请谨慎。
Webhook 准入控制器(v1.9 中的 beta)
截至 v1.10,有两种类型的 Webhook 准入控制器:
-
ValidatingAdmissionWebhook:它可以进行额外的自定义验证来拒绝请求。 -
MutatingAdmissionWebhooks:它可以改变对象,以强制执行默认策略。
如需更多实现信息,请参阅官方文档:
kubernetes.io/docs/admin/extensible-admission-controllers/
另见
以下配方与本节相关:
-
在第二章中,与命名空间一起工作,深入了解 Kubernetes 概念。
-
设置持续交付流水线 在第五章,构建持续交付流水线
-
kubeconfig 中的高级设置 在第八章,高级集群管理
-
使用 ServiceAccount RESTful API 在第八章,高级集群管理
第九章:日志记录和监控
本章将涵盖以下食谱:
-
使用 EFK
-
使用 Google Stackdriver
-
监控主节点和工作节点
介绍
日志记录和监控是 Kubernetes 中最重要的两项任务。然而,Kubernetes 中有许多方式可以实现日志记录和监控,因为有很多开源日志记录和监控应用程序,也有许多公共云服务。
Kubernetes 有一个设置日志记录和监控基础设施的最佳实践,大多数 Kubernetes 配置工具都将其作为附加组件支持。此外,托管的 Kubernetes 服务,如 Google Kubernetes Engine,开箱即集成了 GCP 日志和监控服务。
让我们在你的 Kubernetes 集群上设置日志记录和监控服务。
使用 EFK
在容器世界中,日志管理总是面临技术难题,因为容器有自己的文件系统,当容器崩溃或被驱逐时,日志文件就会消失。此外,Kubernetes 可以轻松地扩展和缩减 Pod,因此我们需要关注一个集中式的日志持久机制。
Kubernetes 有一个用于设置集中式日志管理的附加组件,称为 EFK。EFK 代表 Elasticsearch、Fluentd 和 Kibana。这些应用程序的堆栈为你提供了完整的日志收集、索引和 UI 功能。
准备工作
在 第一章,搭建你自己的 Kubernetes 集群 中,我们使用了几种不同的配置工具来搭建 Kubernetes 集群。根据你使用的 Kubernetes 配置工具,有一种简单的方法来搭建 EFK 堆栈。请注意,Elasticsearch 和 Kibana 是重量级的 Java 应用程序,它们每个至少需要 2 GB 内存。
因此,如果你使用 minikube,你的机器至少应该有 8 GB 内存(推荐 16 GB)。如果你使用 kubespray 或 kops 来设置 Kubernetes 集群,则 Kubernetes 节点应该至少具有 4 核 CPU 和 16 GB 内存(换句话说,如果你有 2 个节点,每个节点应该至少有 2 核 CPU 和 8 GB 内存)。
此外,为了演示如何高效地收集应用程序日志,我们创建了一个额外的命名空间。它将帮助你轻松搜索应用程序日志:
$ kubectl create namespace chap9
namespace "chap9" created
如何实现…
在本食谱中,我们将使用以下 Kubernetes 配置工具来搭建 EFK 堆栈。根据你的 Kubernetes 集群,请阅读本食谱中适当的部分:
-
minikube
-
kubespray(ansible)
-
kops
请注意,对于 Google Cloud Platform 上的 GKE,我们将在下一篇食谱中介绍另一种设置日志基础设施的方法。
使用 minikube 设置 EFK
minikube 为 EFK 提供了开箱即用的附加功能,但默认情况下是禁用的。因此,你需要手动启用 minikube 上的 EFK。EFK 消耗大量堆内存,而 minikube 默认仅分配 2GB 内存,这显然不足以在 minikube 中运行 EFK 堆栈。因此,我们需要明确增大 minikube 的内存大小。
此外,由于在编写本手册时,针对 EFK 做了多个 bug 修复,你应该使用最新版本的 minikube。因此,我们使用 minikube 版本 0.25.2。让我们通过以下步骤配置 minikube 以启用 EFK:
- 如果你已经在运行
minikube,首先停止minikube:
$ minikube stop
- 更新到最新版本的 minikube:
//if you are using macOS
$ brew update
$ brew cask upgrade
//if you are using Windows, download a latest binary from
https://github.com/kubernetes/minikube/releases
- 由于 EFK 消耗大量堆内存,启动
minikube时分配 5GB 内存:
$ minikube start --vm-driver=hyperkit --memory 5120
- 确保
kube-system命名空间中的所有 Pods 都已启动,因为 EFK 依赖于kube-addon-manager-minikube:
$ kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system kube-addon-manager-minikube 1/1 Running 0 1m
kube-system kube-dns-54cccfbdf8-rc7gf 3/3 Running 0 1m
kube-system kubernetes-dashboard-77d8b98585-hkjrr 1/1 Running 0 1m
kube-system storage-provisioner 1/1 Running 0 1m
- 启用
efk附加组件:
$ minikube addons enable efk
efk was successfully enabled
- 等待一会儿;Elasticsearch、Fluentd 和 Kibana Pod 会自动部署在
kube-system命名空间中。等待STATUS变为Running。这通常需要至少 10 分钟才能完成:
$ kubectl get pods --namespace=kube-system
NAME READY STATUS RESTARTS AGE
$ kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system elasticsearch-logging-t5tq7 1/1 Running 0 9m
kube-system fluentd-es-8z2tr 1/1 Running 0 9m
kube-system kibana-logging-dgql7 1/1 Running 0 9m
kube-system kube-addon-manager-minikube 1/1 Running 1 34m
…
- 使用
kubectl logs查看等待状态变为green的 Kibana。这还需要额外的五分钟:
$ kubectl logs -f kibana-logging-dgql7 --namespace=kube-system
{"type":"log","@timestamp":"2018-03-25T18:53:54Z","tags":["info","optimize"],"pid":1,"message":"Optimizing and caching bundles for graph, ml, kibana, stateSessionStorageRedirect, timelion and status_page. This may take a few minutes"}
*(wait for around 5min)*
{"type":"log","@timestamp":"2018-03-25T19:03:10Z","tags":["status","plugin:elasticsearch@5.6.2","info"],"pid":1,"state":"yellow","message":"Status changed from yellow to yellow - No existing Kibana index found","prevState":"yellow","prevMsg":"Waiting for Elasticsearch"}
{"type":"log","@timestamp":"2018-03-25T19:03:15Z","tags":["status","plugin:elasticsearch@5.6.2","info"],"pid":1,"state":"green","message":"Status changed from yellow to green - Kibana index ready","prevState":"yellow","prevMsg":"No existing Kibana index found"}
- 使用
minikube service命令访问 Kibana 服务:
$ minikube service kibana-logging --namespace=kube-system
Opening kubernetes service kube-system/kibana-logging in default browser...
现在,你可以从你的机器访问 Kibana UI。你只需要设置一个索引。由于 Fluentd 持续发送带有索引名称logstash-yyyy.mm.dd的日志,默认的索引模式是logstash-*。点击创建按钮:

使用 kubespray 设置 EFK
kubespray 有一个配置项,决定是否启用 EFK。默认情况下是禁用的,因此你需要通过以下步骤手动启用它:
-
打开
<kubespray dir>/inventory/mycluster/group_vars/k8s-cluster.yaml。 -
在
k8s-cluster.yml文件的第 152 行附近,将efk_enabled的值更改为true:
# Monitoring apps for k8s
efk_enabled: true
- 运行
ansible-playbook命令来更新你的 Kubernetes 集群:
$ ansible-playbook -b -i inventory/mycluster/hosts.ini cluster.yml
- 检查 Elasticsearch、Fluentd 和 Kibana Pod 的
STATUS是否变为 Running;如果你看到Pending状态超过 10 分钟,检查kubectl describe pod <Pod name>查看状态。在大多数情况下,你会看到内存不足的错误。如果是这样,你需要添加更多节点或增加可用的 RAM:
$ kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system calico-node-9wnwn 1/1 Running 0 2m
kube-system calico-node-jg67p 1/1 Running 0 2m
kube-system elasticsearch-logging-v1-776b8b856c-97qrq 1/1 Running 0 1m kube-system elasticsearch-logging-v1-776b8b856c-z7jhm 1/1 Running 0 1m kube-system fluentd-es-v1.22-gtvzg 1/1 Running 0 49s kube-system fluentd-es-v1.22-h8r4h 1/1 Running 0 49s kube-system kibana-logging-57d98b74f9-x8nz5 1/1 Running 0 44s kube-system kube-apiserver-master-1 1/1 Running 0 3m
kube-system kube-controller-manager-master-1 1/1 Running 0 3m
…
- 查看 Kibana 日志,检查状态是否变为
green:
$ kubectl logs -f kibana-logging-57d98b74f9-x8nz5 --namespace=kube-system
ELASTICSEARCH_URL=http://elasticsearch-logging:9200
server.basePath: /api/v1/proxy/namespaces/kube-system/services/kibana-logging
{"type":"log","@timestamp":"2018-03-25T05:11:10Z","tags":["info","optimize"],"pid":5,"message":"Optimizing and caching bundles for kibana and statusPage. This may take a few minutes"}
*(wait for around 5min)*
{"type":"log","@timestamp":"2018-03-25T05:17:55Z","tags":["status","plugin:elasticsearch@1.0.0","info"],"pid":5,"state":"yellow","message":"Status changed from yellow to yellow - No existing Kibana index found","prevState":"yellow","prevMsg":"Waiting for Elasticsearch"}
{"type":"log","@timestamp":"2018-03-25T05:17:58Z","tags":["status","plugin:elasticsearch@1.0.0","info"],"pid":5,"state":"green","message":"Status changed from yellow to green - Kibana index ready","prevState":"yellow","prevMsg":"No existing Kibana index found"}
- 运行
kubectl cluster-info,确认 Kibana 正在运行,并捕获 Kibana 的 URL:
$ kubectl cluster-info
Kubernetes master is running at http://localhost:8080
Elasticsearch is running at http://localhost:8080/api/v1/namespaces/kube-system/services/elasticsearch-logging/proxy
Kibana is running at http://localhost:8080/api/v1/namespaces/kube-system/services/kibana-logging/proxy KubeDNS is running at http://localhost:8080/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
- 为了从你的机器远程访问 Kibana WebUI,使用 ssh 端口转发从你的机器到 Kubernetes 主节点会更方便:
$ ssh -L 8080:127.0.0.1:8080 <Kubernetes master IP address>
- 使用以下 URL 从你的机器访问 Kibana WebUI:
http://localhost:8080/api/v1/namespaces/kube-system/services/kibana-logging/proxy。
现在你可以从你的机器访问 Kibana。你还需要配置索引。只需确保索引名称的默认值为 logstash-*。然后,点击 Create 按钮:

使用 kops 设置 EFK
kops 还提供了一个插件,便于在 Kubernetes 集群上轻松设置 EFK 堆栈。请按照以下步骤在 Kubernetes 上运行 EFK 堆栈:
- 运行
kubectl create来指定 kops EFK 插件:
$ kubectl create -f https://raw.githubusercontent.com/kubernetes/kops/master/addons/logging-elasticsearch/v1.6.0.yaml
serviceaccount "elasticsearch-logging" created
clusterrole "elasticsearch-logging" created
clusterrolebinding "elasticsearch-logging" created
serviceaccount "fluentd-es" created
clusterrole "fluentd-es" created
clusterrolebinding "fluentd-es" created
daemonset "fluentd-es" created
service "elasticsearch-logging" created
statefulset "elasticsearch-logging" created
deployment "kibana-logging" created
service "kibana-logging" created
- 等待所有 Pod 的
STATUS变为Running:
$ kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system dns-controller-dc46485d8-pql7r 1/1 Running 0 5m
kube-system elasticsearch-logging-0 1/1 Running 0 1m kube-system elasticsearch-logging-1 1/1 Running 0 53s kube-system etcd-server-events-ip-10-0-48-239.ec2.internal 1/1 Running 0 5m
kube-system etcd-server-ip-10-0-48-239.ec2.internal 1/1 Running 0 5m
kube-system fluentd-es-29xh9 1/1 Running 0 1m kube-system fluentd-es-xfbd6 1/1 Running 0 1m kube-system kibana-logging-649d7dcc87-mrtzc 1/1 Running 0 1m kube-system kube-apiserver-ip-10-0-48-239.ec2.internal 1/1 Running 0 5m
...
- 检查 Kibana 的日志,等待状态变为
green:
$ kubectl logs -f kibana-logging-649d7dcc87-mrtzc --namespace=kube-system
ELASTICSEARCH_URL=http://elasticsearch-logging:9200
server.basePath: /api/v1/proxy/namespaces/kube-system/services/kibana-logging
{"type":"log","@timestamp":"2018-03-26T01:02:04Z","tags":["info","optimize"],"pid":6,"message":"Optimizing and caching bundles for kibana and statusPage. This may take a few minutes"}
*(wait for around 5min)*
{"type":"log","@timestamp":"2018-03-26T01:08:00Z","tags":["status","plugin:elasticsearch@1.0.0","info"],"pid":6,"state":"yellow","message":"Status changed from yellow to yellow - No existing Kibana index found","prevState":"yellow","prevMsg":"Waiting for Elasticsearch"}
{"type":"log","@timestamp":"2018-03-26T01:08:03Z","tags":["status","plugin:elasticsearch@1.0.0","info"],"pid":6,"state":"green","message":"Status changed from yellow to green - Kibana index ready","prevState":"yellow","prevMsg":"No existing Kibana index found"}
- 运行
kubetl cluster-info来获取 Kibana 的 URL:
$ kubectl cluster-info
Kubernetes master is running at https://api.chap9.k8s-devops.net
Elasticsearch is running at https://api.chap9.k8s-devops.net/api/v1/namespaces/kube-system/services/elasticsearch-logging/proxy
Kibana is running at https://api.chap9.k8s-devops.net/api/v1/namespaces/kube-system/services/kibana-logging/proxy KubeDNS is running at https://api.chap9.k8s-devops.net/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
- 使用
kubectl proxy将你的机器转发到 Kubernetes API 服务器:
$ kubectl proxy --port=8080
Starting to serve on 127.0.0.1:8080
- 使用以下 URL 从你的机器访问 Kibana WebUI:
http://127.0.0.1:8080/api/v1/namespaces/kube-system/services/kibana-logging/proxy。请注意,IP 地址是 127.0.0.1,这是正确的,因为我们正在使用 kubectl proxy。
现在,你可以开始使用 Kibana。按照前面 minikube 和 kubespray 部分中的说明配置索引。
它是如何工作的...
如你所见,已安装的 Kibana 版本会根据 Kubernetes 配置工具的不同而有所不同。但本手册将探讨 Kibana 的基本功能。因此,无需担心版本特定的操作。
让我们启动一个示例应用程序,然后学习如何使用 Kibana 监控应用程序日志:
- 准备一个示例应用程序,它会不断将
DateTime和问候消息打印到stdout:
$ cat myapp.yaml
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
containers:
- image: busybox
name: application
args:
- /bin/sh
- -c
- >
while true; do
echo "$(date) INFO hello";
sleep 1;
done
- 在
chap9命名空间中创建一个示例应用程序:
$ kubectl create -f myapp.yaml --namespace=chap9
pod "myapp" created
-
访问 Kibana WebUI,然后点击 Discover 标签:
-
确保时间范围设置为
Last 15 minutes,然后在搜索框中输入kubernetes.namespace_name: chap9并按下 Enter 键:

在 15 分钟内搜索 chap9 命名空间日志
- 你可以看到
chap9命名空间中的所有日志,如下所示。截图显示了比你预期的更多信息。通过点击kubernetes.host、kubernetes.pod_name和log的添加按钮,只会显示此目的所需的字段:

选择日志列
- 现在,你可以看到这个应用程序的更简单日志视图:

显示自定义 Kibana 视图的最终状态
恭喜!现在你已经在 Kubernetes 集群中建立了一个集中的日志管理系统。你可以观察一些 Pod 的部署,看看如何查看应用程序日志。
还有更多...
上述 EFK 堆栈仅收集 Pod 的日志,因为 Fluentd 正在监控 Kubernetes 节点主机上的 /var/log/containers/*。这足以监控应用程序的行为,但作为 Kubernetes 管理员,你还需要一些 Kubernetes 系统日志,例如主节点和工作节点日志。
有一种简单的方法可以实现与 EFK 堆栈集成的 Kubernetes 系统日志管理;添加一个 Kubernetes 事件导出器(Event Exporter),它会持续监控 Kubernetes 事件。当有新事件发生时,日志会发送到 Elasticsearch。因此,你也可以通过 Kibana 监控 Kubernetes 事件。
我们已准备好一个 Eventer(事件导出器)插件 (raw.githubusercontent.com/kubernetes-cookbook/second-edition/master/chapter9/9-1/eventer.yml)。它基于 Heapster (github.com/kubernetes/heapster),并预计在 EFK 插件之上运行。我们可以使用这个 Eventer 通过 EFK 来监控 Kubernetes 事件:
Heapster 的详细信息将在下一节中描述——监控主节点和节点。
- 将 Eventer 添加到现有的 Kubernetes 集群中:
$ kubectl create -f https://raw.githubusercontent.com/kubernetes-cookbook/second-edition/master/chapter9/9-1/eventer.yml
deployment "eventer-v1.5.2" created
serviceaccount "heapster" created
clusterrolebinding "heapster" created
- 确保 Eventer Pod 的
STATUS为Running:
$ kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system elasticsearch-logging-v1-776b8b856c-9vvfl 1/1 Running 0 9m
kube-system elasticsearch-logging-v1-776b8b856c-gg5gx 1/1 Running 0 9m
kube-system eventer-v1.5.2-857bcc76d9-9gwn8 1/1 Running 0 29s
kube-system fluentd-es-v1.22-8prkn 1/1 Running 0 9m
...
- 使用
kubectl logs来持续观察 Heapster 是否能够捕获事件:
$ kubectl logs -f eventer-v1.5.2-857bcc76d9-9gwn8 --namespace=kube-system
I0327 03:49:53.988961 1 eventer.go:68] /eventer --source=kubernetes:'' --sink=elasticsearch:http://elasticsearch-logging:9200?sniff=false
I0327 03:49:53.989025 1 eventer.go:69] Eventer version v1.5.2
I0327 03:49:54.087982 1 eventer.go:95] Starting with ElasticSearch Sink sink
I0327 03:49:54.088040 1 eventer.go:109] Starting eventer
I0327 03:49:54.088048 1 eventer.go:117] Starting eventer http service
I0327 03:50:00.000199 1 manager.go:100] Exporting 0 events
- 为了测试,打开另一个终端,然后创建一个
nginxPod:
$ kubectl run my-nginx --image=nginx
deployment "my-nginx" created
- 观察 Heapster 的日志,已经捕获了一些新的事件:
I0327 03:52:00.000235 1 manager.go:100] Exporting 0 events
I0327 03:52:30.000166 1 manager.go:100] Exporting 8 events
I0327 03:53:00.000241 1 manager.go:100] Exporting 0 events
-
打开 Kibana,导航至设置 | 索引 | 添加新索引。这将添加一个新的索引。
-
将索引名称设置为
heapster-*,将时间字段名称设置为Metadata.creationTimestamp,然后点击创建:

配置 Heapster 索引
-
返回到发现页面,然后从左侧面板选择
heapster-*索引。 -
选择(点击添加按钮)Message、Source.component 和 Source.host:

选择必要的列
- 现在你可以看到 Kubernetes 系统日志,其中显示了
nginxPod 创建事件,如下所示:

显示 Kibana 中系统日志视图的最终状态
现在,你不仅可以监控应用程序日志,还可以在 EFK 堆栈中监控 Kubernetes 系统日志。通过在 logstash-*(应用程序日志)或 heapster-*(系统日志)之间切换索引,你将拥有一个灵活的日志管理环境。
另见
在本书中,我们学习了如何为 Kubernetes 集群启用 EFK 堆栈。Kibana 是一个强大的工具,你可以使用它创建自己的仪表板,并更高效地查看日志。请访问 Kibana 的在线文档了解如何使用它:
- Kibana 用户指南参考:
www.elastic.co/guide/en/kibana/index.html
使用 Google Stackdriver
在第七章,在 GCP 上构建 Kubernetes 中,我们介绍了 GKE。它有一个集成的日志机制,叫做 Google Stackdriver。在本节中,我们将探讨 GKE 与 Stackdriver 之间的集成。
准备工作
要使用 Stackdriver,你只需要一个 GCP 账户。如果你从未使用过 GCP,请回到并阅读 第七章,在 GCP 上构建 Kubernetes,以设置 GCP 账户和 gcloud 命令行界面。
要在 GKE 上使用 Stackdriver,无需任何操作,因为 GKE 默认使用 Stackdriver 作为日志平台。但如果你想明确启用 Stackdriver,可以在使用 gcloud 命令启动 Kubernetes 时,指定 --enable-cloud-logging,如下所示:
$ gcloud container clusters create my-gke --cluster-version 1.9.4-gke.1 --enable-cloud-logging --network default --zone us-west1-b
如果因为某些原因你的 GKE 没有启用 Stackdriver,你可以使用 gcloud 命令在之后启用它:
$ gcloud container clusters update my-gke --logging-service logging.googleapis.com --zone us-west1-b
如何操作...
为了演示 Stackdriver 与 GKE 的结合使用,我们将在 Kubernetes 上创建一个命名空间,然后启动一个示例 Pod,以便查看 Stackdriver 上的日志,具体步骤如下所示:
- 创建
chap9命名空间:
$ kubectl create namespace chap9
namespace "chap9" created
- 准备一个示例应用程序 Pod:
$ cat myapp.yaml
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
containers:
- image: busybox
name: application
args:
- /bin/sh
- -c
- >
while true; do
echo "$(date) INFO hello";
sleep 1;
done
- 在
chap9命名空间中创建 Pod:
$ kubectl create -f myapp.yaml --namespace=chap9
pod "myapp" created
-
访问 GCP Web 控制台并导航到 Logging | Logs。
-
选择已审计资源 | GKE 容器 | 你的 GKE 集群名称(例如:my-gke) | chap9 命名空间:

选择 chap9 命名空间 Pod 日志
- 作为访问
chap9命名空间日志的替代方法,你可以选择一个高级筛选器。然后,输入以下标准到文本框中并点击提交筛选按钮:

使用高级筛选器
resource.type="container"
resource.labels.cluster_name="<Your GKE name>"
resource.labels.namespace_id="chap9"

输入高级筛选器标准
- 现在,你可以在 Stackdriver 上看到
myapp的日志:

在 Stackdriver 中显示 chap9 Pod 日志
它是如何工作的...
Stackdriver 提供了基本功能,可以按日期、严重性和关键字进行筛选。这有助于监控应用程序的行为。那么,系统级行为,比如主节点和节点活动呢?Stackdriver 也支持搜索系统级别的日志。实际上,fluentd 不仅捕获应用程序日志,还捕获系统日志。通过执行以下步骤,你可以在 Stackdriver 中查看系统日志:
- 选择 GKE 集群操作 | 你的 GKE 名称(例如,my-gke) | 所有位置:
你应该选择所有位置,而不是特定位置,因为一些 Kubernetes 操作日志不包含位置值。

选择 Stackdriver 中的 GKE 系统日志
- 作为替代方法,输入以下高级筛选器:
resource.type="gke_cluster"
resource.labels.cluster_name="<Your GKE name>"

在 Stackdriver 中显示 GKE 系统日志
参见
在这个示例中,我们介绍了 Google Stackdriver。它是 Google Kubernetes Engine 的内置功能。Stackdriver 是一个简单但强大的日志管理工具。此外,Stackdriver 还能够监控系统状态。你可以创建内置或自定义的指标来监控并提供关于事件的警报。这将在下一个示例中详细描述。
此外,请阅读以下章节,回顾 GCP 和 GKE 的基础知识:
- 第七章,在 GCP 上构建 Kubernetes
监控主节点和工作节点
在前面几个示例的过程中,你学习了如何构建自己的集群,运行各种资源,体验不同的使用场景,甚至提升集群管理能力。现在,Kubernetes 集群有了新的视角。在本例中,我们将讨论监控。通过监控工具,用户不仅能了解节点的资源消耗情况,还能监控 Pods 的状态。这将帮助我们在资源利用效率上取得更大提升。
准备就绪
与之前的示例一样,你只需准备一个健康的 Kubernetes 集群。以下命令与 kubectl 一起使用,可以帮助你验证 Kubernetes 系统的状态:
// check the components
$ kubectl get cs
为了后续演示,我们将在 minikube-booted 集群上部署监控系统。然而,它同样适用于任何已安装良好的集群。
如何操作…
本节内容将介绍如何安装监控系统并介绍其仪表盘。该监控系统基于 Heapster(github.com/kubernetes/heapster),它是一个收集和分析资源使用情况的工具。Heapster 与 kubelet 通信,获取机器和容器的资源使用情况。配合 Heapster,我们还使用 influxDB(influxdata.com)进行存储,Grafana(grafana.org)作为前端仪表盘,能够通过多个用户友好的图表展示资源状态:

监控组件的交互
Heapster 从每个节点上的kubelet收集信息,并为其他平台提供数据。在我们的案例中,influxDB 用作保存历史数据的接收端。它允许用户进行进一步的数据分析,例如预测峰值工作负载,然后进行相应的资源调整。我们使用 Grafana 作为一个友好的 Web 控制台;用户可以通过浏览器管理监控状态。此外,kubectl 还具有 top 子命令,提供通过 Heapster 提取集群范围信息的功能:
// try kubectl top before installing Heapster
$ kubectl top node
Error from server (NotFound): the server could not find the requested resource (get services http:heapster:)
该命令返回错误信息。
安装监控系统比预想的要简单得多。通过应用开源社区和公司提供的配置文件,我们可以通过少量命令轻松在 Kubernetes 上设置组件监控:
// installing influxDB
$ kubectl create -f https://raw.githubusercontent.com/kubernetes/heapster/master/deploy/kube-config/influxdb/influxdb.yaml
deployment "monitoring-influxdb" created
service "monitoring-influxdb" created
// installing Heapster
$ kubectl create -f https://raw.githubusercontent.com/kubernetes/heapster/master/deploy/kube-config/influxdb/heapster.yaml
serviceaccount "heapster" created
deployment "heapster" created
//installing Grafana
$kubectl create -f https://raw.githubusercontent.com/kubernetes/heapster/master/deploy/kube-config/influxdb/grafana.yaml
deployment "monitoring-grafana" created
service "monitoring-grafana" created
你会发现,使用在线资源也是创建 Kubernetes 应用程序的可行方案。
它是如何工作的…
安装完 influxDB、Heapster 和 Grafana 后,让我们来学习如何获取资源的状态。首先,你可以现在使用 kubectl top。检查节点和 Pods 的使用情况,同时验证监控功能:
// check the status of nodes
$ kubectl top node
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
minikube 236m 11% 672Mi 35%
// check the status of Pods in Namespace kube-system
$ kubectl top pod -n kube-system
NAME CPU(cores) MEMORY(bytes)
heapster-5c448886d-k9wt7 1m 18Mi
kube-addon-manager-minikube 36m 32Mi
kube-dns-54cccfbdf8-j65x6 3m 22Mi
kubernetes-dashboard-77d8b98585-z8hch 0m 12Mi
monitoring-grafana-65757b9656-8cl6d 0m 13Mi
monitoring-influxdb-66946c9f58-hwv8g 1m 26Mi
目前,kubectl top仅涵盖节点和 Pods,并且只显示它们的 CPU 和 RAM 使用情况。
根据kubectl top的输出,m表示什么,关于 CPU 使用量的数量?它代表“毫”,如同毫秒和毫米一样。Millicpu 被认为是 10^(-3) CPU。例如,如果 Heapster Pod 使用 1 m CPU,这时它只消耗 0.1%的 CPU 计算能力。
介绍 Grafana 仪表板
现在,让我们来看看 Grafana 仪表板。在我们的案例中,对于 minikube 启动的集群,我们应该打开代理,以便从本地主机访问 Kubernetes 集群:
$ kubectl proxy
Starting to serve on 127.0.0.1:8001
你可以通过以下网址访问 Grafana:http://localhost:8001/api/v1/namespaces/kube-system/services/monitoring-grafana/proxy/。使我们能够查看网页的魔法是由 Kubernetes 的 DNS 服务器和代理完成的:
在非 minikube Kubernetes 中访问 Grafana 仪表板
要通过浏览器访问 Grafana,取决于节点的网络配置和 Grafana 的 Kubernetes 服务。请按照以下步骤将网页转发到你的客户端:
-
升级 Grafana 的服务类型:我们应用的配置文件创建了一个 ClusterIP 服务的 Grafana。你应该将其更改为
NodePort或LoadBalancer,以便将 Grafana 暴露到外部。 -
检查防火墙:确保你的客户端或负载均衡器能够访问集群中的节点。
-
通过目标端口访问仪表板:你可以通过简单的 URL 来访问 Grafana,而不是像我们在 minikube 集群中使用的详细 URL,例如
NODE_ENTRYPOINT:3000(默认情况下,Grafana 请求端口 3000)或负载均衡器的入口点。

Grafana 的主页
在 Grafana 的默认设置中,我们有两个仪表板,Cluster和Pods。Cluster仪表板显示节点的资源利用情况,例如 CPU、内存、网络流量和存储。Pods仪表板为每个 Pod 提供类似的图表,你可以检查 Pod 中每个容器的情况:

查看 Pod kube-dns 的 Pod 仪表板
正如前面的截图所示,例如,我们可以观察命名空间kube-system中的kube-dns Pod 中单个容器的 CPU 利用率,这个 Pod 是 DNS 服务器的集群。你会发现这个 Pod 中有三个容器,kubedns、dnsmasq和sidecar,图表中的线条分别表示容器的 CPU 限制、请求和实际使用情况。
创建新的指标来监控 Pod
对于正在运行的应用程序,度量标准是我们可以收集和用来分析其行为和性能的数据。度量标准可以来自系统方面,比如 CPU 的使用,也可以基于应用程序的功能,比如某些功能的请求频率。Heapster 提供了几个监控用的度量标准(github.com/kubernetes/heapster/blob/master/docs/storage-schema.md)。我们将向您展示如何自己创建一个定制面板。请按照以下步骤操作:
- 转到 Pod 的仪表板,并将网页拖到底部。有一个名为“ADD ROW”的按钮;点击它以添加一个度量标准。然后,选择图表类别作为表达这个度量标准的新面板:

使用图表表达添加一个新的度量标准
- 一个空的面板块出现了。继续点击进行进一步配置。当你选择面板后显示编辑块时,选择编辑:

开始编辑面板
- 首先,给你的面板取一个名字。例如,
CPU Rate。我们想创建一个显示 CPU 利用率的面板:

在“通用”页面上给面板一个标题
-
设置以下参数以特定数据查询。请参考以下截图:
-
FROM:
cpu/usage_rate -
WHERE:
type = pod_container -
AND:
namespace_name=$namespace, pod_name=$podname value -
按
tag(container_name)分组 -
由
$tag_container_name别名
-

数据查询参数的 CPU 利用率度量标准
- 是否显示任何状态线?如果没有,在显示页面中修改配置将帮助您建立最佳外观的图表。使 Null 值连接,您将找到显示的线:

编辑度量标准的外观。检查 Null 值以“连接”显示线
- 就这样!随意关闭编辑模式。您现在每个 Pod 都有一个新的度量标准。
只需尝试发现 Grafana 仪表板和 Heapster 监控工具的更多功能。通过监控系统的信息,您将获取有关系统、服务和容器的进一步详细信息。
还有更多...
我们建立了一个基于 Heapster 的监控系统,该系统由 Kubernetes 团队维护。然而,许多专注于容器集群的工具和平台纷纷涌现,旨在支持社区,比如 Prometheus(prometheus.io)。另一方面,公有云可能默认在虚拟机上运行守护进程来抓取指标,并提供相应的服务来执行相关操作。我们不必在集群内部构建此类服务。接下来,我们将介绍 AWS 和 GCP 上的监控方法。你可能希望查看 Chapter 6,在 AWS 上构建 Kubernetes,以及 Chapter 7,在 GCP 上构建 Kubernetes,来构建一个集群并学习更多概念。
在 AWS 上监控你的 Kubernetes 集群
在使用 AWS 时,我们通常依赖 AWS CloudWatch(aws.amazon.com/cloudwatch/)来进行监控。你可以创建一个仪表板,选择任何你想要的基本指标。CloudWatch 已经为你收集了大量的指标:

使用 AWS CloudWatch 创建新的指标
但是,对于 Kubernetes 资源,例如 Pods,需要将它们的自定义指标通过手动配置发送到 CloudWatch。如果是通过 kops 安装,建议使用 Heapster 或 Prometheus 来构建你的监控系统。
AWS 有自己的容器集群服务——Amazon ECS。这可能是 AWS 没有深度支持 Kubernetes 的原因,我们需要通过 kops 和 terraform 构建集群,并配合其他附加服务。然而,根据最近的新闻,将推出一项名为 Amazon Elastic Container Service for Kubernetes(Amazon EKS)的新服务。我们可以期待 Kubernetes 与 AWS 其他服务的集成。
在 GCP 上监控你的 Kubernetes 集群
在我们查看 GCP 的监控平台之前,GKE 集群的节点应该允许扫描任何应用状态:
// add nodes with monitoring scope
$ gcloud container clusters create my-k8s-cluster --cluster-version 1.9.7-gke.0 - -machine-type f1-micro --num-nodes 3 --network k8s-network --zone us-central1-a - -tags private --scopes=storage-rw,compute-ro, https://www.googleapis.com/auth/monitoring
Google Stackdriver 提供了一个混合云环境下的系统监控。除了它自己的 GCP,它还可以覆盖你在 AWS 上的计算资源。要访问其 Web 控制台,可以在左侧菜单中找到其相应部分。Stackdriver 中有多个服务类别,选择 Monitoring 来查看相关功能。
作为新用户,你将获得 30 天的免费试用。初始配置非常简单;只需启用一个账户并绑定你的项目。你可以避免安装代理和设置 AWS 账户,因为我们只是想查看 GKE 集群。一旦成功登录 Stackdriver,点击左侧面板的 Resources,并在基础设施类型下选择 Kubernetes Engine:

GKE 在 Stackdriver 上的主页面
已经设置了多个用于计算从节点到容器的资源的指标。请花时间探索并查看官方介绍,了解更多特性:cloud.google.com/kubernetes-engine/docs/how-to/monitoring。
另见
本文展示了如何在 Kubernetes 系统中监控机器。然而,明智的做法是研究主组件和守护进程的配方。你可以更多地了解工作流程和资源使用情况。此外,既然我们已经与多个服务合作建立了我们的监控系统,再次回顾关于 Kubernetes 服务的配方将帮助你更清晰地了解如何构建这个监控系统:
-
第一章中的探索架构配方,构建你自己的 Kubernetes 集群
-
第二章中的与服务协作配方,走进 Kubernetes 概念
Kubernetes 是一个不断发展并快速升级的项目。跟上进度的推荐方式是查看其官方网站上的新特性:kubernetes.io。你也可以随时在 GitHub 上获取新的 Kubernetes 版本:github.com/kubernetes/kubernetes/releases。保持 Kubernetes 系统的最新状态,并通过实践学习新特性,是持续接触 Kubernetes 技术的最佳方法。


浙公网安备 33010602011771号