Kubernetes-秘籍-全-

Kubernetes 秘籍(全)

原文:zh.annas-archive.org/md5/6F444487B7AC74DB6092F54D9EA36B7A

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

近年来,微服务架构的趋势,将单片应用程序重构为多个微服务。容器简化了由微服务构建的应用程序的部署。容器管理、自动化和编排已成为关键问题。Kubernetes 就是为了解决这些问题而存在的。

本书是一本实用指南,提供逐步提示和示例,帮助您在私有和公共云中构建和运行自己的 Kubernetes 集群。跟着本书学习将使您了解如何在 Kubernetes 中部署和管理应用程序和服务。您还将深入了解如何扩展和更新实时容器,以及如何在 Kubernetes 中进行端口转发和网络路由。您将学习如何通过本书的实际示例构建一个稳健的高可用性集群。最后,您将通过集成 Jenkins、Docker 注册表和 Kubernetes 来构建一个持续交付流水线。

本书适合对象

如果您已经玩了一段时间的 Docker 容器,并希望以现代方式编排您的容器,那么这本书是您的正确选择。本书适合那些已经了解 Docker 和容器技术,并希望进一步探索以找到更好的方式来编排、管理和部署容器的人。本书非常适合超越单个容器,与容器集群一起工作,学习如何构建自己的 Kubernetes,并使其与您的持续交付流水线无缝配合。

本书涵盖内容

第一章《构建您自己的 Kubernetes 集群》解释了如何使用各种部署工具构建自己的 Kubernetes 集群,并在其上运行第一个容器。

第二章《走进 Kubernetes 概念》涵盖了我们需要了解的 Kubernetes 的基本和高级概念。然后,您将学习如何通过编写和应用配置文件来组合它们以创建 Kubernetes 对象。

第三章《玩转容器》解释了如何扩展和缩减容器,并执行滚动更新而不影响应用程序的可用性。此外,您将学习如何部署容器来处理不同的应用程序工作负载。它还将带您了解配置文件的最佳实践。

第四章,“构建高可用性集群”,提供了构建高可用性 Kubernetes 主节点和 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并注册,以便将文件直接发送到您的邮箱。

您可以按照以下步骤下载代码文件:

  1. 登录或注册www.packtpub.com

  2. 选择“支持”选项卡。

  3. 点击“代码下载和勘误”。

  4. 在搜索框中输入书名,然后按照屏幕上的说明操作。

一旦文件下载完成,请确保您使用最新版本的解压软件解压文件夹:

  • Windows 的 WinRAR/7-Zip

  • Mac 的 Zipeg/iZip/UnRarX

  • Linux 的 7-Zip/PeaZip

该书的代码包也托管在 GitHub 上,网址为 github.com/PacktPublishing/Kubernetes-Cookbook-Second-Edition。如果代码有更新,将在现有的 GitHub 存储库中更新。

我们还有其他代码包,来自我们丰富的书籍和视频目录,可在 github.com/PacktPublishing/ 上找到。看看吧!

下载彩色图片

我们还提供了一个 PDF 文件,其中包含本书中使用的屏幕截图/图表的彩色图片。您可以在这里下载:www.packtpub.com/sites/default/files/downloads/KubernetesCookbookSecondEdition_ColorImages.pdf

使用的约定

本书中使用了许多文本约定。

CodeInText:表示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。这是一个例子:“准备以下 YAML 文件,这是一个简单的部署,启动两个 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

粗体:表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词会以这种方式出现在文本中。这是一个例子:“安装很简单,所以我们可以选择默认选项,然后点击“下一步”。”

警告或重要说明会出现在这样。提示和技巧会出现在这样。

章节

在本书中,您会经常看到几个标题(准备工作如何做…它是如何工作的…还有更多…另请参阅)。

为了清晰地说明如何完成一个配方,请使用以下部分:

准备工作

本节告诉您在配方中可以期待什么,并描述了为配方设置任何所需的软件或初步设置。

如何做…

这一部分包含了遵循食谱所需的步骤。

它是如何工作的...

这一部分通常包括对上一部分发生的事情的详细解释。

还有更多...

这一部分包括了有关食谱的额外信息,以使您对食谱更加了解。

另请参阅

这一部分提供了与食谱相关的其他有用信息的链接。

第一章:构建您自己的 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-apiserverkube-schedulerkube-controller-manager。Hypercube,这个包装二进制文件,可以启动所有这些守护进程。

此外,Kubernetes 命令行界面 kubect 可以控制 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 容器运行时作为默认引擎。

节点还是从属节点?

术语“slave”在计算机行业中用于表示集群工作节点;然而,它也与歧视有关。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 -Liptables -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 (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 re: Invent 2017 年推出,由 AWS 上的 Kubernetes 服务管理。

Kubernetes 也可以通过自定义解决方案在不同的云和本地 VM 上运行。在本章中,我们将在 macOS 桌面机上使用 minikube 构建 Kubernetes。

准备工作

minikube 在 macOS 上的 Linux VM 上运行 Kubernetes。它依赖于 hypervisor(虚拟化技术),例如 VirtualBox (www.virtualbox.org),VMWare fusion (www.vmware.com/products/fusion.html)或 hyperkit (github.com/moby/hyperkit)。此外,我们还需要 Kubernetes 命令行界面CLIkubectl,用于通过 hypervisor 连接和控制 Kubernetes。

使用 minikube,您可以在 macOS 上运行整个 Kubernetes 堆栈,包括 Kubernetes 主节点和 CLI。建议 macOS 具有足够的内存来运行 Kubernetes。默认情况下,minikube 使用 VirtualBox 作为 hypervisor。

然而,在本章中,我们将演示如何使用 hyperkit,这是最轻量级的解决方案。由于 Linux VM 消耗 2GB 内存,建议至少 4GB 内存。请注意,hyperkit 是建立在 macOS 的 hypervisor 框架(developer.apple.com/documentation/hypervisor)之上的,因此需要 macOS 10.10 Yosemite 或更高版本。

以下图表显示了 kubectl、hypervisor、minikube 和 macOS 之间的关系:

如何做...

macOS 没有官方的软件包管理工具,如 Linux 上的 yum 和 apt-get。但是有一些对 macOS 有用的工具。Homebrew (brew.sh)是最流行的软件包管理工具,管理许多开源工具,包括 minikube。

要在 macOS 上安装Homebrew,请执行以下步骤:

  1. 打开终端,然后输入以下命令:
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
  1. 安装完成后,您可以输入/usr/local/bin/brew help来查看可用的命令选项。

如果您刚刚在 macOS 上安装或升级 Xcode,则可能会导致Homebrew安装停止。在这种情况下,打开 Xcode 以接受许可协议,或者事先输入sudo xcodebuild -license

  1. 接下来,为 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
  1. 接下来,让我们安装 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。

  1. 为了通过homebrew-cask安装 minikube,只需简单地键入以下命令:
//add "cask" option
$ brew cask install minikube
  1. 如果您从未在您的机器上安装过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
  1. 现在您已经准备就绪!以下命令显示了您的 macOS 上是否已安装所需的软件包:
//check installed package by homebrew
$ brew list
kubernetes-cli

//check installed package by homebrew-cask
$ brew cask list
minikube

工作原理...

minikube 适用于使用以下命令在您的 macOS 上设置 Kubernetes,该命令下载并启动 Kubernetes VM stet,然后配置 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 

获取所有必要的软件包后,执行以下步骤:

  1. 等待几分钟,直到 Kubernetes 集群设置完成。

  2. 使用kubectl version来检查 Kubernetes 主版本,使用kubectl get cs来查看组件状态。

  3. 此外,使用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
  1. 现在您可以在您的机器上开始使用 Kubernetes。以下部分描述了如何使用kubectl命令来操作 Docker 容器。

  2. 请注意,在某些情况下,您可能需要维护 Kubernetes 集群,例如启动/停止 VM 或完全删除它。以下命令维护 minikube 环境:

命令 目的
minikube start --vm-driver=hyperkit 使用 hyperkit 驱动程序启动 Kubernetes VM
minikube stop 停止 Kubernetes VM
minikube delete 删除 Kubernetes VM 镜像
minikube ssh ssh 到 Kubernetes VM guest
minikube ip 显示 Kubernetes VM(节点)的 IP 地址
minikube update-context 检查并更新~/.kube/config,如果 VM IP 地址发生变化
minikube dashboard 打开 Web 浏览器连接 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 上的任何 Hypervisor 来运行 Linux 虚拟机,从头开始设置 Kubernetes,但使用 minikube (github.com/kubernetes/minikube) 是在 Windows 上构建 Kubernetes 集群的最快方法。请注意,本教程并不适用于生产环境,因为它将在 Windows 上的 Linux 虚拟机上设置 Kubernetes。

准备工作

在 Windows 上设置 minikube 需要一个 Hypervisor,要么是 VirtualBox (www.virtualbox.org) 要么是 Hyper-V,因为 minikube 再次使用 Windows 上的 Linux 虚拟机。这意味着您不能使用 Windows 虚拟机(例如,在 macOS 上通过 parallels 运行 Windows 虚拟机)。

然而,kubectl,Kubernetes CLI,支持一个可以通过网络连接到 Kubernetes 的 Windows 本机二进制文件。因此,您可以在 Windows 机器上设置一个便携式的 Kubernetes 堆栈套件。

以下图表显示了 kubectl、Hypervisor、minikube 和 Windows 之间的关系:

Windows 8 专业版或更高版本需要 Hyper-V。虽然许多用户仍在使用 Windows 7,但在本教程中我们将使用 VirtualBox 作为 minikube 的 Hypervisor。

操作步骤

首先,需要 Windows 的 VirtualBox:

  1. 前往 VirtualBox 网站 (www.virtualbox.org/wiki/Downloads) 下载 Windows 安装程序。

  2. 安装非常简单,所以我们可以选择默认选项并点击下一步:

  1. 接下来,创建用于存储 minikube 和 kubectl 二进制文件的 Kubernetes 文件夹。让我们在 C: 驱动器的顶部创建 k8s 文件夹,如下面的屏幕截图所示:

  1. 此文件夹必须在命令搜索路径中,因此打开系统属性,然后转到高级选项卡。

  2. 单击“环境变量...”按钮,然后选择“路径”,然后单击“编辑...”按钮,如下面的屏幕截图所示:

  1. 然后,追加 c:\k8s,如下所示:

  1. 单击“确定”按钮后,注销并重新登录到 Windows(或重新启动)以应用此更改。

  2. 接下来,下载 Windows 版的 minikube。它是一个单一的二进制文件,因此可以使用任何网络浏览器下载 github.com/kubernetes/minikube/releases/download/v0.26.1/minikube-windows-amd64,然后将其复制到 c:\k8s 文件夹,但将文件名更改为 minikube.exe

  3. 接下来,下载 Windows 版的 kubectl,它可以与 Kubernetes 进行通信。它也像 minikube 一样是单一的二进制文件。因此,下载 storage.googleapis.com/kubernetes-release/release/v1.10.2/bin/windows/amd64/kubectl.exe,然后也将其复制到 c:\k8s 文件夹中。

  4. 最终,您将在 c:\k8s 文件夹中看到两个二进制文件,如下面的屏幕截图所示:

如果您正在运行防病毒软件,可能会阻止您运行 kubectl.exeminikube.exe。如果是这样,请更新您的防病毒软件设置,允许运行这两个二进制文件。

工作原理...

让我们开始吧!

  1. 打开命令提示符,然后键入 minikube start,如下面的屏幕截图所示:

  1. minikube 下载 Linux VM 镜像,然后在 Linux VM 上设置 Kubernetes;现在,如果打开 VirtualBox,您可以看到 minikube 客户端已经注册,如下面的屏幕截图所示:

  1. 等待几分钟,完成 Kubernetes 集群的设置。

  2. 根据下面的屏幕截图,键入 kubectl version 来检查 Kubernetes 主版本。

  3. 使用kubectl get nodes命令检查 Kubernetes 节点是否准备就绪:

  1. 现在您可以在您的机器上开始使用 Kubernetes 了!再次强调,Kubernetes 正在 Linux 虚拟机上运行,如下截图所示。

  2. 使用minikube ssh允许您访问运行 Kubernetes 的 Linux 虚拟机:

因此,任何基于 Linux 的 Docker 镜像都可以在您的 Windows 机器上运行。

  1. 键入minikube ip以验证 Linux 虚拟机使用的 IP 地址,还可以使用minikube dashboard,打开默认的 Web 浏览器并导航到 Kubernetes UI,如下截图所示:

  1. 如果您不再需要使用 Kubernetes,请键入minikube stop或打开 VirtualBox 停止 Linux 虚拟机并释放资源,如下截图所示:

另请参阅

这个教程描述了如何在 Windows 操作系统上使用 minikube 设置 Kubernetes 集群。这是开始使用 Kubernetes 的最简单方法。它还描述了 kubectl,Kubernetes 命令行接口工具,这是控制 Kubernetes 的入口点。

通过 kubeadm 在 Linux 上设置 Kubernetes 集群

在这个教程中,我们将展示如何在 Linux 服务器上使用 kubeadm(github.com/kubernetes/kubeadm)创建 Kubernetes 集群。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
  • 网络工具包已安装。 ethtoolebtables 是 kubeadm 的两个必需实用程序。它们可以通过apt-getyum软件包管理工具下载和安装。

如何做...

将分别介绍 Ubuntu 和 CentOS 两种 Linux 操作系统的安装程序,因为它们有不同的设置。

软件包安装

让我们首先获取 Kubernetes 软件包!需要在软件包管理系统的源列表中设置下载的存储库。然后,我们可以通过命令行轻松地将它们安装。

Ubuntu

要在 Ubuntu 中安装 Kubernetes 软件包,请执行以下步骤:

  1. 一些存储库是使用 HTTPS 的 URL。必须安装apt-transport-https软件包才能访问 HTTPS 端点:
$ sudo apt-get update && sudo apt-get install -y apt-transport-https
  1. 下载访问 Google Cloud 软件包的公钥,并添加如下:
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
OK
  1. 接下来,为 Kubernetes 软件包添加一个新的源列表:
$ sudo bash -c 'echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" > /etc/apt/sources.list.d/kubernetes.list'
  1. 最后,安装 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 软件包,请执行以下步骤:

  1. 与 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
  1. 现在,我们准备通过yum命令从 Kubernetes 源基地拉取软件包:
// on Kubernetes master
$ sudo yum install -y kubelet kubeadm kubectl
// on Kubernetes node
$ sudo yum install -y kubelet
  1. 无论是什么操作系统,都要检查你得到的软件包的版本!
// 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 是否在您的机器上运行 Kubernetes。此外,为了避免在执行 kubeadm 时出现关键错误,我们将展示系统和 kubelet 上必要的服务配置。与主服务器一样,请在 Kubernetes 节点上设置以下配置,以使 kubelet 与 kubeadm 正常工作。

CentOS 系统设置

在 CentOS 中还有其他额外的设置,以使 Kubernetes 行为正确。请注意,即使我们不使用 kubeadm 来管理 Kubernetes 集群,运行 kubelet 时应考虑以下设置:

  1. 禁用 SELinux,因为 kubelet 不完全支持 SELinux:
// check the state of SELinux, if it has already been disabled, bypass below commands
$ sestatus

我们可以通过以下命令禁用 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 is required
$ sudo reboot
  1. 启用 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 的状态时,您可能会担心看到状态显示为激活(自动重启);您可能会通过journalctl命令看到详细日志,如下所示:

错误:无法加载客户端 CA 文件/etc/kubernetes/pki/ca.crt:打开/etc/kubernetes/pki/ca.crt:没有那个文件或目录

别担心。kubeadm 会负责创建证书授权文件。它在服务配置文件/etc/systemd/system/kubelet.service.d/10-kubeadm.conf中定义,通过参数KUBELET_AUTHZ_ARGS。没有这个文件,kubelet 服务就不会正常工作,所以要不断尝试重新启动守护进程。

通过 kubeadm 启动所有主节点守护程序。 值得注意的是,使用 kubeadm 需要 root 权限才能实现服务级别的特权。 对于任何 sudoer,每个 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 控制的组:

  1. 首先,启用并启动服务kubelet。 每台 Kubernetes 机器都应该在其上运行kubelet
$ sudo systemctl enable kubelet && sudo systemctl start kubelet
  1. 完成后,使用带有输入标记令牌和主节点的 IP 地址的kubeadm加入命令,通知主节点它是一个受保护和授权的节点。您可以通过kubeadm命令在主节点上获取令牌:
// 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
  1. 在前面的输出中,如果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

  1. 请确保主节点的防火墙不会阻止端口6443的任何流量,这是用于 API 服务器通信的端口。一旦在屏幕上看到“成功建立连接”这几个字,就是时候与主节点确认组是否有了新成员了:
// 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 设置主节点时,有六个阶段:

  1. 生成服务的证书文件和密钥:证书文件和密钥用于跨节点通信期间的安全管理。它们位于/etc/kubernetes/pki目录中。以 kubelet 为例。它在未经身份验证的情况下无法访问 Kubernetes API 服务器。

  2. 编写 kubeconfig 文件kubeconfig文件定义了 kubectl 操作的权限、身份验证和配置。在这种情况下,Kubernetes 控制器管理器和调度器有相关的kubeconfig文件来满足任何 API 请求。

  3. 创建服务守护进程 YAML 文件:kubeadm 控制下的服务守护进程就像在主节点上运行的计算组件一样。与在磁盘上设置部署配置一样,kubelet 将确保每个守护进程处于活动状态。

  4. 等待 kubelet 处于活动状态,将守护进程作为 pod 运行:当 kubelet 处于活动状态时,它将启动/etc/kubernetes/manifests目录下描述的服务 pod。此外,kubelet 保证保持它们激活,如果 pod 崩溃,将自动重新启动。

  5. 为集群设置后配置:仍然需要设置一些集群配置,例如配置基于角色的访问控制(RBAC)规则,创建命名空间和标记资源。

  6. 应用附加组件:DNS 和代理服务可以与 kubeadm 系统一起添加。

当用户输入 kubeadm 并加入 Kubernetes 节点时,kubeadm 将完成像主节点一样的前两个阶段。

如果您在早期版本的 Kubernetes 中面临繁重和复杂的设置过程,使用 kubeadm 设置 Kubernetes 集群会让人感到宽慰。kubeadm 减少了配置每个守护进程并逐个启动它们的开销。用户仍然可以在 kubelet 和主服务上进行自定义,只需修改熟悉的文件10-kubeadm.conf/etc/kubernetes/manifests下的 YAML 文件。Kubeadm 不仅有助于建立集群,还增强了安全性和可用性,为您节省时间。

另请参阅

我们讨论了如何构建 Kubernetes 集群。如果您准备在其上运行第一个应用程序,请查看本章的最后一个配方并运行容器!对于对集群的高级管理,您还可以查看本书的第八章,“高级集群管理”:

  • kubeconfig 中的高级设置,在第八章,“高级集群管理”

通过 Ansible 在 Linux 上设置 Kubernetes 集群(kubespray)

如果您熟悉配置管理,例如 Puppet、Chef 和 Ansible,kubespray(github.com/kubernetes-incubator/kubespray)是从头开始设置 Kubernetes 集群的最佳选择。它提供了支持大多数 Linux 发行版和 AWS、GCP 等公共云的 Ansible 剧本。

Ansible(www.ansible.com)是一种基于 Python 的 SSH 自动化工具,可以根据配置将 Linux 配置为所需状态,称为 playbook。本教程描述了如何使用 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 可能是安装 Ansible 的最佳选择,因为大多数 macOS 和 Linux 发行版默认情况下都预装了 Python。为了检查您的 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 或更高版本):

  1. 确认pip是否已安装,类似于 Python 命令,使用-V
//use capital V
$ pip -V
pip 9.0.1 from /Library/Python/2.7/site-packages (python 2.7)
  1. 另一方面,如果您看到以下结果,您需要安装pip
//this result shows you don't have pip yet
$ pip -V
-bash: pip: command not found
  1. 为了安装 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:

  1. 一旦您安装了pip,您可以使用以下命令安装 Ansible:
//ran by privileged user (sudo)
$ sudo pip install ansible

pip扫描您的 Python 并安装 Ansible 所需的库,因此可能需要几分钟才能完成。

  1. 成功安装了 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 公钥身份验证,则可以跳过此步骤。

  1. 为了从您的 Ansible 机器创建一个 ssh 公钥/私钥对,输入以下命令:
//with –q means, quiet output
$ ssh-keygen -q
  1. 它会要求您设置一个密码。您可以设置或跳过(空),但您必须记住它。

  2. 一旦您成功创建了密钥对,您可以看到私钥为~/.ssh/id_rsa,公钥为~/.ssh/id_rsa.pub。您需要将公钥附加到目标机器的~/.ssh/authorized_keys下,如下面的屏幕截图所示:

  1. 您需要将您的公钥复制粘贴到所有 Kubernetes 主节点和节点候选机器上。

  2. 确保您的 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,而不是一个二进制文件,您可以直接在 Ansible 机器上下载最新版本(截至 2018 年 5 月,版本 2.5.0 是最新版本)的 ziptar.gz 并使用以下命令解压缩:

//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 地址的库存文件:

  1. 在 inventory 目录下有一个示例库存文件,所以您可以使用以下命令进行复制:
//copy sample to mycluster
ansible_machine$ cp -rfp inventory/sample inventory/mycluster 
//edit hosts.ini
ansible_machine$ vi inventory/mycluster/hosts.ini 
  1. 在本教程中,我们使用具有以下 IP 地址的目标机器:
  • Kubernetes 主节点:10.128.0.2

  • Kubernetes 节点:10.128.0.4

  1. 在这种情况下,hosts.ini 应该采用以下格式:

  1. 请更改 IP 地址以匹配您的环境。

请注意,主机名(my-master-1my-node-1)将根据此 hosts.ini 设置为 kubespray playbook,因此可以自由分配有意义的主机名。

运行 Ansible ad hoc 命令以测试您的环境

在运行 kubespray playbook 之前,让我们检查 hosts.ini 和 Ansible 本身是否正常工作:

  1. 为此,请使用 Ansible ad hoc 命令,使用 ping 模块,如下截图所示:

  1. 此结果表示“成功”。但如果您看到以下错误,可能是 IP 地址错误或目标机器已关闭,请先检查目标机器:

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

  1. 使用 -b 选项,它实际上尝试在目标机器上执行 sudo。如果您看到“成功”,那就准备好了!转到 它是如何工作的… 部分来运行 kubespray。

如果不幸看到一些错误,请参考以下部分解决 Ansible 问题。

Ansible 故障排除

理想情况是使用相同的 Linux 发行版、版本、设置和登录用户。然而,根据政策、兼容性和其他原因,环境会有所不同。Ansible 灵活并且可以支持许多用例来运行sshsudo

需要指定 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 端口

另一种情况是您可能需要在某些特定端口号上运行 ssh 守护程序,而不是默认端口号22。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 还有一个配置文件ansible.cfg,位于kubespray目录的顶部。它为 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中的所有先前配置:

有关更多详细信息,请访问docs.ansible.com/ansible/latest/intro_configuration.html中的 Ansible 配置文档。

它是如何工作的...

现在您可以开始运行 kubepray playbook:

  1. 您已经创建了一个名为inventory/mycluster/hosts.ini的清单文件。除了hosts.ini,您还需要检查和更新inventory/mycluster/group_vars/all.yml中的全局变量配置文件。

  2. 有很多变量被定义,但至少需要将一个变量bootstrap_osnone更改为目标 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中的文档。

  1. 最后,您可以执行 playbook。使用ansible-playbook命令而不是 Ansible 命令。ansible-playbook 根据 playbook 中定义的任务和角色运行多个 Ansible 模块。

  2. 运行 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 指定。

  1. 根据机器规格和网络带宽,大约需要 5 到 10 分钟才能完成。但最终您可以看到PLAY RECAP,如下面的截图所示,以查看是否成功:

  1. 如果您看到像前面的截图中的failed=0,则您已成功设置了 Kubernetes 集群。您可以 ssh 到 Kubernetes 主节点并运行/usr/local/bin/kubectl命令来查看状态,如下面的截图所示:

  1. 前面的截图显示您已成功设置了 Kubernetes 版本 1.10.2 的主节点和节点。您可以继续使用kubectl命令在接下来的章节中配置 Kubernetes 集群。

  2. 不幸的是,如果您看到失败计数超过 0,那么 Kubernetes 集群可能没有正确设置。由于失败的原因有很多,没有单一的解决方案。建议您附加-v选项以查看来自 Ansible 的更详细的输出,如下面的代码所示:

//use –b (become), -i (inventory) and –v (verbose)
$ ansible-playbook -v -b -i inventory/mycluster/hosts.ini cluster.yml
  1. 如果失败是超时,只需重新运行 ansible-playbook 命令可能会解决问题。因为 Ansible 被设计为幂等性,如果您多次重新执行 ansible-playbook 命令,Ansible 仍然可以正确配置。

  2. 如果失败是在运行 ansible-playbook 后更改目标 IP 地址(例如,重复使用 Ansible 机器设置另一个 Kubernetes 集群),则需要清理事实缓存文件。它位于/tmp目录下,所以您只需删除这个文件,如下面的截图所示:

另请参阅

本节描述了如何在 Linux 操作系统上使用 kubespray 设置 Kubernetes 集群。这是一个支持主要 Linux 发行版的 Ansible 剧本。Ansible 很简单,但由于支持任何情况和环境,您需要关注一些不同的用例。特别是在 ssh 和 sudo 相关的配置方面,您需要更深入地了解 Ansible 以适应您的环境。

在 Kubernetes 中运行您的第一个容器

恭喜!您已经在之前的配方中构建了自己的 Kubernetes 集群。现在,让我们开始运行您的第一个容器,nginx(nginx.org/),这是一个开源的反向代理服务器、负载均衡器和 Web 服务器。除了这个配方,您还将创建一个简单的 nginx 应用程序,并将其暴露给外部世界。

准备就绪

在 Kubernetes 中运行第一个容器之前,最好检查一下您的集群是否处于健康状态。列出以下项目的清单将使您的kubectl子命令稳定且成功,而不会因后台服务引起未知错误:

  1. 检查主节点守护程序。检查 Kubernetes 组件是否正在运行:
// get the components status
$ kubectl get cs
NAME                 STATUS    MESSAGE              ERROR
controller-manager   Healthy   ok
scheduler            Healthy   ok
etcd-0               Healthy   {"health": "true"}
  1. 检查 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'. 
  1. 检查所有节点是否准备就绪:
$ kubectl get nodes
NAME       STATUS    ROLES     AGE       VERSION
ubuntu01   Ready     master    20m       v1.10.2
ubuntu02   Ready     <none>    2m        v1.10.2

理想的结果应该类似于前面的输出。您可以成功执行kubectl命令并获得响应而无错误。如果所检查的项目中有任何一个未能达到预期结果,请根据您使用的管理工具在前面的配方中检查设置。

  1. 检查 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

让我们继续看看所有 pod 的当前状态,使用kubectl get pods。通常,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 来检查 pod 的当前状态,使用 kubectl describe pods $pod_name来检查 pod 中的详细信息。如果您输入了错误的镜像名称,可能会收到ErrImagePull错误消息,如果您从私有存储库或注册表中拉取镜像而没有适当的凭据,可能会收到ImagePullBackOff消息。如果您长时间收到Pending状态并检查节点容量,请确保您没有运行超出节点容量的副本。如果出现其他意外错误消息,您可以停止 pod 或整个复制控制器,以强制主节点重新安排任务。

您还可以检查部署的详细信息,以查看所有的 pod 是否已准备就绪:

// 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 计算引擎),使用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 并暴露了端口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.9192.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>

您应该能够使用 Web 浏览器看到以下页面:

另请参阅

在本节中,我们已经运行了我们的第一个容器。继续阅读下一章,以获取更多关于 Kubernetes 的知识:

  • 第二章,深入了解 Kubernetes 概念

第二章:通过 Kubernetes 概念

在本章中,我们将涵盖以下配方:

  • 链接 Pod 和容器

  • 使用 ReplicaSets 管理 Pods

  • 部署 API

  • 处理服务

  • 使用卷

  • 处理机密

  • 处理名称

  • 使用命名空间

  • 使用标签和选择器

介绍

在本章中,我们将首先在 Kubernetes 系统上创建不同类型的资源。为了在微服务结构中实现您的应用程序,阅读本章中的配方将是理解 Kubernetes 资源概念并加以巩固的良好开端。在您在 Kubernetes 中部署应用程序之后,您可以开始进行可伸缩和高效的容器管理,并完成微服务的 DevOps 交付流程。

Kubernetes 概述

使用 Kubernetes 非常容易,可以使用命令行界面CLI)或 API(RESTful)。本节将描述通过 CLI 控制 Kubernetes。本章中使用的 CLI 版本为 1.10.2。

安装 Kubernetes 主节点后,可以运行以下kubectl命令。它显示 kubectl 和 Kubernetes 主节点的版本(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,它会尝试访问 localhost,否则您需要使用--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命令或kubectl create -f命令与以下 YAML 文件:

  1. 使用run命令:
$ kubectl run my-first-nginx --image=nginx "my-first-nginx"
  1. 使用create -f命令与 YAML 文件:
$ 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
  1. 如果您想要查看部署的状态,请输入以下kubectl get命令:
$ kubectl get deployment NAME             DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE my-first-nginx   1         1         1            1           4s
  1. 如果您还想要支持缩写,输入以下内容:
$ kubectl get deploy NAME             DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE my-first-nginx   1         1         1            1           38s
  1. 如果要删除这些资源,请输入以下kubectl delete命令:
$ kubectl delete deploy my-first-nginx deployment.extensions "my-first-nginx" deleted
  1. 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 组件:

  • 在 macOS 上使用 minikube 设置 Kubernetes 集群在 Windows 上使用 minikube 设置 Kubernetes 集群在第一章,构建您自己的 Kubernetes 集群

  • 在 Linux 上使用 kubeadm 设置 Kubernetes 集群在第一章,构建您自己的 Kubernetes 集群

  • 在 Linux 上使用 kubespray(Ansible)设置 Kubernetes 集群在第一章,构建您自己的 Kubernetes 集群

链接 Pod 和容器

Pod 是一个或多个容器的组合,是 Kubernetes 中最小的可部署单元。Pod 始终是共同定位和共同调度的,并在共享上下文中运行。每个 Pod 都由以下 Linux 命名空间隔离:

  • 进程 IDPID)命名空间

  • 网络命名空间

  • 进程间通信IPC)命名空间

  • Unix 时间共享UTS)命名空间

在容器之前的世界中,它们将在同一台物理或虚拟机上执行。

构建自己的应用程序堆栈 Pod(例如,Web 服务器和数据库)是很有用的,这些 Pod 由不同的 Docker 镜像混合而成。

准备工作

您必须拥有一个 Kubernetes 集群,并确保 Kubernetes 节点可以访问 Docker Hub(hub.docker.com)以便下载 Docker 镜像。

如果您正在运行 minikube,请先使用 minikube ssh 登录到 minikube VM,然后运行 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 的步骤:

  1. 登录到 Kubernetes 机器(如果使用 minikube,则无需登录),并准备以下 YAML 文件。它定义了启动 nginx 容器和 CentOS 容器。

  2. 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"]
  1. 然后,执行 kubectl create 命令启动 my-first-pod,如下所示:
$ kubectl create -f my-first-pod.yaml pod "my-first-pod" created 

这需要几秒到几分钟的时间,具体取决于 Docker Hub 和 Kubernetes 节点规格的网络带宽。

  1. 您可以使用 kubectl get pods 命令查看状态,如下所示:
//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)都已准备就绪。

  1. 让我们检查 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 将两个不同的容器 nginxCentOS 链接到相同的 Linux 网络命名空间中。

它是如何工作的...

在启动 Pod 时,Kubernetes 调度程序将分派给 kubelet 进程来处理在一个 Kubernetes 节点上启动 nginxCentOS 容器的所有操作。

以下图示了这两个容器和 Pod;这两个容器可以通过本地主机网络进行通信,因为在 Pod 容器内部,它们共享网络接口:

一个 Pod 有两个容器,可以通过本地主机进行通信

如果您有两个或更多节点,可以使用 -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 命令查看正在运行的容器,如下所示:

属于我的第一个 Pod 的容器列表

您可能注意到my-first-pod包含三个容器;centosnginxpause正在运行,而不是两个。因为我们需要保留的每个 Pod 都属于特定的 Linux 命名空间,如果 CentOS 和 nginx 容器都死掉了,命名空间也会被销毁。因此,暂停容器只是留在 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 有两个容器,centosnginx。因此,在您的 Kubernetes 集群上总共有四个容器正在运行,如下图所示:

从 my-first-pod 复制 Pod 到 my-second-pod 如果您想部署更多相同的 Pod,请考虑使用部署(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 等来进行 Pods 的高级操作:

  • 使用 ReplicaSets 管理 Pods

  • 部署 API

  • 使用 Services 工作

  • 使用标签和选择器

使用 ReplicaSets 管理 Pods

ReplicaSet 是 Kubernetes 中指向 Pod 副本的 API 对象的术语。其想法是能够控制一组 Pod 的行为。ReplicaSet 确保用户指定数量的 Pods 始终运行。如果 ReplicaSet 中的一些 Pod 崩溃并终止,系统将自动在健康节点上使用原始配置重新创建 Pods,并保持一定数量的进程持续运行。在更改集的大小时,用户可以轻松地扩展或缩小应用程序。根据这个特性,无论您是否需要 Pod 的副本,您都可以始终依赖 ReplicaSet 进行自动恢复和可伸缩性。在这个配方中,您将学习如何使用 ReplicaSet 管理您的 Pods:

两个节点上的 ReplicaSet 及其 Pods

ReplicaSet 通常处理一个应用程序层。如前图所示,我们启动了一个具有三个 Pod 副本的 ReplicaSet。一些机制细节列在下面:

  • kube-controller-manager 守护程序有助于保持资源以期望的状态运行。例如,图表中 ReplicaSet 的期望状态是三个 Pod 副本。

  • Kubernetes 主节点上的 kube-scheduler 守护程序负责将任务分配给健康的节点。

  • ReplicaSet 的选择器用于决定它覆盖的 Pods。如果 Pod 的标签中的键值对包括 ReplicaSet 选择器中的所有项目,则此 Pod 属于此 ReplicaSet。正如您将看到的那样,图表显示三个 Pod 属于 ReplicaSet 的管理范围。即使 Pod 2 具有不同的env标签,它也被选中,因为另外两个标签roleproject与 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 主节点是否实用:

  • 检查守护程序是否正在运行:主节点上应该有三个工作中的守护进程:apiserverschedulercontroller-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 的第一个标签位于此资源的元数据下方,就在名称的下方,仅用于描述。然而,在模板的元数据下方,用于 Pods 的另一个标签值也用于标识。ReplicaSet 负责具有其选择器覆盖的标签的 Pods。

在我们的示例配置文件中,ReplicaSet 的选择器寻找具有project: My-Happy-Webrole: frontend标签的 Pods。由于我们在此 ReplicaSet 的控制下初始化 Pods,Pods 的标签应该明确包含选择器关心的内容。在创建具有不正确标记的 Pods 的 ReplicaSet 时,您可能会收到以下错误消息:选择器`与模板`标签`不匹配

现在,让我们通过这个文件创建 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 之后,子命令getdescribe可以帮助我们捕获其信息和 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 副本已成功创建并分配了唯一名称。您也可以通过名称检查 Pods:

// 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 的配置

名为editpatchreplace的子命令可以帮助更新 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 只通过它们的标签来管理 Pods。可能 Pod 模板和 Pod 的配置是不同的。这也意味着可以通过标签修改将独立的 Pod 添加到集合中。

让我们通过创建一个类似于本教程开头图表的 ReplicaSet 来评估选择器和标签的概念:

ReplicaSet 将覆盖具有与其选择器中描述的相同标签的 Pod

首先,我们将创建一个带有标签 project: My-Happy-Webrole: frontendenv: test的 CentOS Pod:

// 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

添加了这个命令后,一个带有我们指定标签的独立 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-Webrole:frontend(忽略env标签)的 Pod。去检查独立的 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 ...

同样,一旦我们删除了集合,独立的 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

还有更多...

有多种用于 Pod 管理的 Kubernetes 资源。鼓励用户利用各种资源类型来满足不同的目的。让我们将下面列出的资源类型与 ReplicaSet 进行比较:

  • Deployment:在一般情况下,Kubernetes Deployments 通常与 ReplicaSet 一起用于完整的 Pod 管理:容器滚动更新,负载均衡和服务暴露。

  • Job:有时,我们希望 Pods 作为作业而不是服务运行。Kubernetes 作业适用于这种情况。您可以将其视为具有终止约束的 ReplicaSet。

  • DaemonSet:与 ReplicaSet 相比,Kubernetes DaemonSet 保证指定的集合在集群中的每个节点上都在运行。也就是说,每个节点上都有一个 ReplicaSet 的子集。

要了解更多想法和指导,您可以在第三章的Playing with Containers中查看食谱Ensuring flexible usage of your containers

另请参阅

现在您了解了 ReplicaSet 的概念。继续查看本章中的以下食谱,以获取更多 Kubernetes 资源,这将使您能够探索 ReplicaSet 的神奇效果:

  • 部署 API

  • Working with Services

  • Working with labels an selectors

此外,由于您已经使用配置文件构建了一个简单的 ReplicaSet,请参考有关为 Kubernetes 资源创建自己的配置文件的更多详细信息:

  • Working with configuration files部分在第三章的Playing with Containers

部署 API

部署 API 是在 Kubernetes 版本 1.2 中引入的。它正在取代复制控制器。通过复制控制器实现的滚动更新和回滚功能是通过客户端端(kubectl命令和REST API)实现的,kubectl在更新复制控制器时需要保持连接。另一方面,部署在服务器端负责滚动更新和回滚的过程。一旦该请求被接受,客户端可以立即断开连接。

因此,部署 API 被设计为更高级别的 API 来管理 ReplicaSet 对象。本节将探讨如何使用部署 API 来管理 ReplicaSets。

准备就绪

要创建部署对象,通常使用kubectl run命令或准备描述部署配置的 YAML/JSON 文件。此示例使用kubectl run命令创建my-nginx部署对象:

//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

如您所见,部署对象my-nginx创建了一个ReplicaSet,其具有标识符:<部署名称>-<十六进制哈希>。然后 ReplicaSet 创建了三个具有标识符的 Pod:<ReplicaSet id>-<random id>

直到 Kubernetes 版本 1.8,<部署名称>-<pod-template-hash value (number)>被用作 ReplicaSet 标识符,而不是十六进制哈希。

有关更多详细信息,请查看拉取请求:github.com/kubernetes/kubernetes/pull/51538

此图示了部署ReplicaSetPod之间的关系:

部署、副本集和 Pod 的关系图

由于这种关系,如果您对 my-nginx 部署对象执行 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

这个例子只是一个简单的 createdelete,这个时刻很容易理解部署对象和 ReplicaSet 对象的 1:1 关系。然而,一个部署对象可以管理多个 ReplicaSet 以保留历史记录。因此,实际关系是 1:N,如下图所示:

部署维护 ReplicaSet 历史

为了理解 1:N 的关系,让我们重新创建这个部署对象,并进行一些更改,看看部署如何管理 ReplicaSet 历史。

如何做到这一点...

您可以运行 kubectl run 命令来重新创建 my-nginx,或者编写一个生成相同结果的部署配置文件。这是一个了解部署配置文件的绝佳机会。

这个例子相当于 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

这些参数按键和值排序,描述如下:

描述
apiVersion apps/v1 直到 Kubernetes v1.8,它使用了 apps/v1Beta1,v1.8 使用了 apps/v1Beta2,然后 v1.9 或更高版本使用 apps/v1
kind deployment 表示这是一组部署配置
metadata.name my-nginx 部署的名称
spec.replicas 3 欲拥有三个 Pod
spec.selector.matchLabels run:my-nginx 控制具有此标签的 ReplicaSet/Pod
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 文件创建一个部署,使用 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

您可以在上述代码中看到OldReplicaSetsNewReplicaSet属性,这是 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部署以将容器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变成了以前的ReplicaSetmy-nginx-54bb7bbcf9),而NewReplicaSet变成了my-nginx-77769b7666。请注意,您可以在新的ReplicaSet成功启动之前看到OldReplicaSets属性,因此一旦新的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

正如您所看到的,在旧的ReplicaSetmy-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 image 命令相同的操作,因此您可以看到 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 版本时,您可以使用kubectl rollout undo命令和--to-revision选项。例如,如果您想要回滚到历史记录中的第 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.2ReplicaSet,然后激活使用nginx版本1.12ReplicaSet,如下所示:

$ 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

另请参阅

在本节中,您了解了部署的概念。这是 Kubernetes ReplicaSet 生命周期管理中的一个重要核心功能。它允许我们实现部署和回滚功能,并可以集成到 CI/CD 中。在接下来的章节中,您将看到详细的部署和回滚操作:

  • 在第三章的更新实时容器部分,玩转容器*

  • 在第五章的设置持续交付管道部分,构建持续交付管道*

处理服务

网络服务是一个接收请求并提供解决方案的应用程序。客户端通过网络连接访问服务。他们不必了解服务的架构或其运行方式。客户端唯一需要验证的是服务的端点是否可以访问,然后遵循其使用策略以获取服务器的响应。Kubernetes 服务有类似的想法。在到达它们的功能之前,不需要了解每个 Pod。对于 Kubernetes 系统外的组件,它们只需通过公开的网络端口访问 Kubernetes 服务以与运行的 Pod 通信。不需要了解容器的 IP 和端口。在 Kubernetes 服务的背后,我们可以为我们的容器程序实现零停机更新而不费吹灰之力:

Kubernetes 服务通过 Pod 的标签和它们的选择器进行覆盖

上图显示了服务的基本结构,并实现了以下概念:

  • 部署一样,服务将请求定向到具有包含服务选择器的标签的 Pod。换句话说,服务选择的 Pod 是基于它们的标签。

  • 发送到服务的请求负载将分发到三个 Pod。

  • 部署与 ReplicaSet 一起,确保运行的 Pod 的数量达到其期望的状态。它监视服务的 Pod,确保它们对于接管服务的职责是健康的。

  • 服务是对分组 Pod 的抽象层,允许 Pod 在节点之间进行扩展。

在这个示例中,您将学习如何为请求在 Pods 前创建服务。

准备就绪

在应用 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

一旦您看到输出,进程 ID2326kube-proxy,在本地主机上的端口10249上监听,节点已准备好用于 Kubernetes 服务。继续验证您的 Kubernetes 集群中的所有节点是否都在运行kube-proxy

如何做...

如前一节所述,Kubernetes 服务通过选择相应的标签来暴露 Pod。然而,还有另一个配置我们需要注意:网络端口。如下图所示,服务和 Pod 都有自己的键值对标签和端口:

服务和 Pod 之间的网络端口映射

因此,在创建服务时,需要设置服务的选择器并将服务暴露的端口绑定到容器端口。如果它们中的任何一个未能正确设置,客户端将无法获得响应或者会收到连接被拒绝的错误。

我们可以通过 CLI 或配置文件定义和创建新的 Kubernetes 服务。在这里,我们将解释如何通过命令部署服务。在以下命令中,子命令exposedescribe用于各种场景。对于文件格式的创建,建议阅读第三章中的使用配置文件部分,进行详细讨论。

为不同资源创建服务

您可以将服务附加到 Pod、部署、Kubernetes 系统外的端点,甚至另一个服务。我们将在本节中依次展示这些内容。创建 Kubernetes 服务的方式与以下命令格式类似:kubectl expose $RESOURCE_TYPE $RESOURCE_NAME [OTHER TAGS]kubectl expose -f $CONFIG_FILE。资源类型(Pod、部署和服务)都受到子命令expose的支持。配置文件也是如此,它遵循限制类型。因此,为了后续的演示,我们将通过配置文件将新创建的服务附加到端点。

为 Pod 创建服务

由服务覆盖的 Kubernetes Pods 需要标签,以便服务可以识别应该负责的对象。在下面的命令中,我们首先为 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

您可能会发现,根据前面的命令,我们没有为这个服务分配任何选择器。尽管如此,由于服务nginx-service承担了 Podnginx-pod的端口转发任务,它将以 Pod 的标签作为其选择器。继续并使用子命令describe检查服务的详细信息:

// "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>

现在你可以看到,为了保证责任,这个成功暴露的服务只是复制了 Pod 的标签作为它的选择器。Endpoints后面的值列表是 Pod 的 IP 和它的暴露端口80。此外,服务将 Pod 的标签作为自己的标签。根据这个例子,可以通过访问10.96.107.213:8080来访问 Pod。

除了服务的选择器之外,如果用户绕过了一些参数,它们可以自动配置。一个参数是 Pod 的标签;另一个是服务的名称;另一个是服务的暴露端口。让我们看看如何管理这个简单的 Pod 和服务:

// 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>

在这里,我们可以看到服务从 Pod 那里继承了名称、标签和端口。选择器被分配了一个名为run的虚拟标签,值为 Pod 的名称,这与 Pod nginx-no-label的虚拟标签相同。用户也应该通过端口80访问服务。对于这样简单的设置,你也可以尝试使用以下命令同时创建 Pod 和服务:

// 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

为具有外部 IP 的部署创建服务

Kubernetes 部署是服务的理想资源类型。对于由 ReplicaSet 和 Deployment 监督的 Pod,Kubernetes 系统有一个控制器管理器来监视它们的生命周期。将现有服务绑定到另一个部署,也有助于更新程序的版本或状态。对于以下命令,我们首先创建一个部署,然后附加一个具有外部 IP 的服务:

// 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

让我们继续检查新创建的服务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>

除了服务 IP(在前面的命令中为10.100.109.230)可以在 Kubernetes 系统内访问之外,现在服务还可以通过 Kubernetes 系统之外的外部 IP(例如192.168.122.102)进行连接。虽然 Kubernetes 主节点能够与每个节点通信,但在这种情况下,我们可以向服务发送请求,比如以下命令:

$ curl 192.168.122.102:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

为没有选择器的端点创建服务

首先,我们将创建一个指向外部服务的终端点。Kubernetes 终端点是一个抽象,使得 Kubernetes 之外的组件(例如其他系统中的数据库)成为 Kubernetes 资源的一部分。它为混合环境提供了一个可行的用例。要创建一个终端点,需要 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的终端点,它指向官方 Kubernetes 网站主机的 IP 地址(kubernetes.io)。不要紧,这个终端点转发到一个普通的 HTML;我们只是拿这个终端点作为一个例子。正如前面提到的,终端点不是 Kubernetes API 支持的资源,用于暴露:

// Give it a try!
$ kubectl expose -f k8s-endpoint.yaml
error: cannot expose a { Endpoints}

在 Kubernetes 中,终端点不仅代表外部服务;内部 Kubernetes 服务也是 Kubernetes 终端点。您可以使用命令kubectl get endpoints检查终端点资源。您会发现,并不是只有一个终端点k8s-ep(您刚刚创建的),而是许多与前几页中的服务同名的终端点。当创建一个带有选择器并暴露某些资源(如 Pod、部署或其他服务)的服务时,同时会创建一个同名的终端点。

因此,我们仍然可以使用相同的名称创建与终端点关联的服务,如下模板所示:

$ cat endpoint-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: k8s-ep
spec:
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 80

终端点和服务之间的关系是通过资源名称建立起来的。对于服务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

  • 添加或更新配置是可行的:我们可以添加新的配置,比如添加会话亲和性;或者我们可以更新服务的端口,就像这里,我们改为打开端口8081而不是8080

  • 避免更改目标端口:因为目标端口与 Pod 的 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

这是因为服务也是 Kubernetes 端点。因此,尽管我们分别创建了服务和端点,一旦它们被视为一个单元工作,当服务被删除时,端点也将被删除。因此,错误消息表达了没有名为k8s-ep的端点,因为它已经随着服务的删除而被删除。

工作原理...

在网络协议栈上,Kubernetes 服务依赖于传输层,与 覆盖网络kube-proxy 协同工作。Kubernetes 的覆盖网络通过在预配置的地址空间中分配子网租约并将网络配置存储在 etcd 中来构建集群网络;另一方面,kube-proxy 通过 iptables 设置帮助将来自服务端点的流量转发到 Pod。

代理模式和服务 kube-proxy 目前有三种不同的实现方法:userspaceiptablesipvs。这些模式会影响客户端请求如何通过 Kubernetes 服务到达特定的 Pod:

  • userspacekube-proxy 为每个本地节点上的服务打开一个随机端口,称为代理端口,然后更新 iptables 规则,捕获发送到服务的任何请求并将其转发到代理端口。最终,发送到代理端口的任何消息都将传递给服务覆盖的 Pod。这种方式效率较低,因为流量需要经过 kube-proxy 进行路由到 Pod。

  • iptables:与 userspace 模式一样,也需要 iptables 规则来重定向客户端流量。但是没有代理端口作为中介。速度更快,但需要注意 Pod 的存活状态。默认情况下,如果目标 Pod 失败,请求没有重试到另一个 Pod 的方式。为了避免访问不健康的 Pod,需要对 Pod 进行健康检查并及时更新 iptables

  • ipvsipvs 是 Kubernetes v1.9 中的 beta 功能。在这种模式下,kube-proxy 在服务和其后端集之间建立了称为 netlink 的接口。ipvs 模式解决了userspaceiptables 的缺点;它甚至更快,因为路由规则存储在内核空间的哈希表结构中,而且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.localcluster.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 服务有四种类型:ClusterIPNodePortLoadBalancerExternalName。在本教程的操作步骤部分,我们只演示了默认类型ClusterIPClusterIP类型表示 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>
...

在这种情况下,我们演示创建一个暴露CNAME kubernetes.ioExternalName服务:

$ 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>处放置服务类型,例如clusteripnodeportloadbalancerexternalname。使用这种方法,我们无法指定服务的选择器。就像我们在那一节中创建的NodePort服务一样,只创建了一个默认选择器app: my-nginx,我们必须将此标签分配给稍后创建的部署test-nodeport。除了类型ExternalName,服务类型可以使用子命令expose和标签type来创建。尝试使用kubectl expose为现有资源创建NodePort服务!

另请参阅

要获得 Kubernetes 服务的最佳实践,建议阅读第二章中的以下示例,深入了解 Kubernetes 概念

  • 部署 API

  • 使用机密

  • 使用标签和选择器

有更多高级知识可以使您的服务更加功能强大和灵活。敬请关注:

  • 在第三章的Playing with Containers部分,转发容器端口*

  • 在第三章的Playing with Containers部分,确保容器的灵活使用*

使用卷

容器中的文件是临时的。当容器终止时,文件会消失。Docker 引入了数据卷来帮助我们持久化数据(docs.docker.com/engine/admin/volumes/volumes)。然而,当涉及到多个主机时,作为容器集群,跨所有容器和主机管理卷以进行文件共享或动态配置卷是很困难的。Kubernetes 引入了卷,它在容器的整个生命周期中与 Pod 一起存在。它支持各种类型的卷,包括流行的网络磁盘解决方案和不同公共云中的存储服务。以下是其中一些:

卷类型 存储提供程序
emptyDir 本地主机
hostPath 本地主机
glusterfs GlusterFS 集群
downwardAPI Kubernetes Pod 信息
nfs NFS 服务器
awsElasticBlockStore 亚马逊弹性块存储
gcePersistentDisk Google Compute Engine 持久磁盘
azureDisk Azure 磁盘存储
projected Kubernetes 资源;目前支持secretdownwardAPIconfigMap
secret Kubernetes Secret 资源
vSphereVolume vSphere VMDK 卷
gitRepo Git 仓库

准备工作

在开始在 Kubernetes 中使用卷时,需要存储提供程序,除了emptyDir,当 Pod 被删除时,它将被删除。对于其他存储提供程序,在使用它们之前,必须在 Pod 定义中构建文件夹、服务器或集群。动态配置在 Kubernetes 版本 1.6 中被提升为稳定版本,它允许您根据支持的云提供商来配置存储。

在本节中,我们将详细介绍emptyDirhostPathnfsglusterfsdownwardAPIgitRepo的细节。Secret用于存储凭据,将在下一节介绍。另一方面,Projected是一种将其他卷资源组合在一个单一挂载点下的方法。由于它只支持secretdownwardAPIconfigMap,我们将在 Secret 部分介绍这一点。其余的卷类型具有类似的 Kubernetes 语法,只是具有不同的后端卷实现。

如何做...

卷在 pod 定义的卷部分中以唯一名称定义。每种类型的卷都有不同的配置要设置。一旦定义了卷,就可以在容器规范的 volumeMounts 部分中挂载它们。volumeMounts.namevolumeMounts.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> 挂载到 /data-mount,以供 Pod 使用。如果创建一个具有多个容器的 Pod,所有这些容器都将使用相同的源挂载到相同的目标 /data-mount。默认的挂载传播是 rprivate,这意味着主机上的任何挂载点在容器中是不可见的,反之亦然。

通过将 emptyDir.medium 设置为 Memory,可以将 emptyDir 挂载为 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 中的内存限制约束。有关容器资源约束的更多信息,请参阅本章中的 使用命名空间

如果 Pod 中有多个容器,Kubectl exec命令将是kubectl exec <pod 名称> <容器名称> <命令>

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)作为nfs 卷挂载到你的 Pod 上。多个 Pod 可以挂载和共享相同的nfs 卷中的文件。存储在nfs 卷中的数据将在 Pod 的整个生命周期中持久存在。在使用nfs 卷之前,你必须创建自己的 NFS 服务器,并确保 Kubernetes 的 minions 上安装了nfs-utils软件包。

在你离开之前,检查一下你的 NFS 服务器是否正常工作。你应该检查/etc/exports文件,使用适当的共享参数和目录,并使用mount -t nfs <nfs 服务器>:<共享名称> <本地挂载点>命令来检查它是否可以在本地挂载。

具有 NFS 类型的卷的配置文件与其他类型类似,但卷定义中需要nfs.servernfs.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 名称>来描述你的 Pod,以检查挂载状态。如果成功挂载,它应该显示条件。就绪为 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 服务器>:<共享名称>挂载到/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 服务器>:/<卷名称> <本地挂载点>,您可以检查 GlusterFS 系统是否可以成功挂载。

由于 GlusterFS 中的卷副本必须大于1,让我们假设我们在服务器gfs1gfs2上有两个副本,并且卷名称是gvol

首先,我们需要创建一个作为gfs1gfs2桥接的端点:

$ 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,它是卷名称gvolglusterfs.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 将转储到名为path的文件中的指定元数据列在fieldRef下,并将<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 容器 容器的 CPU 限制
requests.cpu 容器 容器的 CPU 请求
limits.memory 容器 容器的内存限制
requests.memory 容器 容器的内存请求
limits.ephemeral-storage 容器 容器的临时存储限制
requests.ephemeral-storage 容器 容器的临时存储请求

如果范围是 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"的存在。

如果在容器范围内,我们将不得不指定容器名称:

# 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 提供了PersistentVolumesPersistentVolumeClaim来抽象存储提供程序和存储消费者的细节。

持久卷

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

我们可以看到这里有三个参数:capacityaccessModespersistentVolumeReclaimPolicycapacity是这个PersistentVolume的大小。现在,accessModes是基于存储提供程序的功能,并且可以在分配期间设置为特定模式。例如,NFS 支持同时多个读者和写入者,然后我们可以将accessModes指定为ReadWriteOnceReadOnlyManyReadWriteMany中的一个。现在,persistentVolumeReclaimPolicy用于定义PersistentVolume释放时的行为。当前支持的策略是nfshostPath的保留和回收。在保留模式下,您必须自己清理卷;另一方面,Kubernetes 将在回收模式下清理卷。

PV 就像一个节点资源。我们可以使用kubectl get pv来查看当前分配的 PVs:

# list current PVs
$ kubectl get pv
NAME LABELS CAPACITY ACCESSMODES STATUS CLAIM REASON AGE
pvnfs01 <none> 3Gi RWO Bound default/pvclaim01 37m

接下来,我们需要将PersistentVolumePersistentVolumeClaim绑定,以便将其作为卷挂载到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

它将类似于其他卷类型的语法。只需在卷定义中添加persistentVolumeClaimclaimName。我们已经准备好了!让我们检查细节,看看我们是否成功挂载了它:

# 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
...

我们可以看到我们在 Podnginx中挂载了一个卷,类型为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需要请求存储类。最后一个条件是微不足道的;管理员必须配置存储类以使动态配置工作:

存储类动态分配 PV 并将其与 PVC 关联

默认存储类各种各样,基本上取决于您的基础云提供商。存储类是定义底层存储提供程序的抽象方式。它们根据不同类型的提供程序具有不同的语法。默认存储类可以更改,但不能删除。默认存储类具有注释storageclass.beta.kubernetes.io/is-default-class=true。删除该注释可以禁用动态配置。将注释移动到另一个存储类可以切换默认存储类。如果没有存储类具有该注释,当有新的PersistentVolumeClaim时,将不会触发动态配置。

gcePersistentDisk

gcePersistentDisk卷将Google 计算引擎GCE持久磁盘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。然后,让我们使用这个 PVC 来创建一个 Pod:

# 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卷挂载了一个亚马逊网络服务弹性块存储AWS EBS)卷。这是为亚马逊 EC2 提供持久块存储的服务。就像 GCE 持久磁盘一样,我们可以静态或动态地进行配置。

要静态配置它,管理员必须事先通过 AWS 控制台或 AWS CLI 创建一个 EBS 卷。以下是如何将现有的 EBS 卷挂载到部署中的容器的示例:

// 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:AttachVolumeec2:CreateVolumeec2:DetachVolumeec2: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。

最后,我们通过在规范中指定persistentVolumeClaim来创建一个带有这个卷的部署:

// 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-protectionkubernetes.io/pvc-protection终结器将被StorageObjectInUseProtection准入控制器添加到 PV 或 PVC 中。然后,当对象删除请求被发送时,准入控制器将进行预删除检查,并查看是否有任何 Pod 正在使用它。这将防止数据丢失。

参见

通过在 Pods 或 ReplicaSet 规范中声明,可以挂载卷。查看以下配方以唤起您的记忆:

  • 在第二章的Pods 工作部分,走进 Kubernetes 概念*

  • 在《走进 Kubernetes 概念》的第二章中的使用副本集部分

  • 在《走进 Kubernetes 概念》的第二章中的使用 Secrets部分

  • 在《高级集群管理》的第八章中的在节点中设置资源部分

  • 在《高级集群管理》的第八章中的身份验证和授权部分

使用 Secrets

Kubernetes Secrets 以键值格式管理编码后的信息。它可以是密码、访问密钥或令牌。使用 Secrets,用户不必在配置文件中暴露敏感数据。Secrets 可以减少凭据泄露的风险,并使我们的资源配置更有条理。

目前,有三种类型的 Secrets:

Generic/Opaque 是我们应用程序中使用的默认类型。Docker 注册表用于存储私有 Docker 注册表的凭据。TLS Secret 用于存储用于集群管理的 CA 证书包。

Kubernetes 为访问 API 服务器的凭据创建了内置的 Secrets。

准备工作

在使用 Secrets 之前,我们必须记住,Secret 应该在依赖的 Pods 之前创建,这样依赖的 Pods 才能正确引用它。此外,Secrets 有 1MB 的大小限制。这对于在单个 Secret 中定义一堆信息是有效的。但是,Secret 并不适用于存储大量数据。对于配置数据,请考虑使用ConfigMaps。对于大量的非敏感数据,请考虑使用卷代替。

如何做...

在以下示例中,我们将演示如何创建一个 Generic/Opaque Secret,并在 Pod 中使用它,假设我们有一个访问令牌需要在 Pod 内使用。

创建一个 Secret

创建 Secret 有两种方法。第一种是在命令行中使用kubectl create secret,另一种是在配置文件中直接创建资源。

使用 kubectl create 命令行

通过使用kubectl create secret命令行,您可以从文件、目录或文字值创建一个 Secret。使用这种方法,您不需要自己对 Secret 进行编码。Kubernetes 会为您完成这项工作:

从文件

  1. 如果文件是 Secret 的来源,我们首先必须创建一个包含我们敏感数据的文本文件:
// assume we have a sensitive credential named access token.
# cat 2-7-1_access-token
9S!g0U61699r
  1. 接下来,我们可以使用命令行中的kubectl create secret来创建 Secret。语法是:
Kubectl create secret <secret-type> --from-file <file1> (--from-file <file2> ...)
  1. 在我们的情况下,我们使用通用的 Secret 类型,因为访问令牌既不是 Docker 注册表镜像拉取 Secrets 也不是 TLS 信息:
# kubectl create secret generic access-token --from-file 2-7-1_access-token
secret "access-token" created
  1. 您可以使用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
  1. 您可以使用 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 内部访问 Secret 方面,需要在容器规范中添加env部分,如下所示:

// 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

在前面的例子中,我们将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

前面的例子将secret-volume挂载到 Pod 内部的/secret挂载点。/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 值时,它不会动态更改。

删除秘密

删除秘密,只需使用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

投影卷是将多个卷源组合到同一挂载点的一种方式。目前,它支持 Secrets、ConfigMapdownwardAPI

以下是我们在本章中使用的 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

另请参阅

  • 第二章**中的Working with Volumes部分,Walking through Kubernetes Concepts*

  • 第三章中的使用配置文件部分,与容器一起玩

  • 第五章中的将单片式转换为微服务使用私有 Docker 注册表部分,构建持续交付管道

  • 第七章中的kubeconfig 中的高级设置部分,在 GCP 上构建 Kubernetes

使用名称

当您创建任何 Kubernetes 对象,如 Pod、Deployment 和 Service 时,可以为其分配一个名称。Kubernetes 中的名称在空间上是唯一的,这意味着您不能在 Pod 中分配相同的名称。

准备工作

Kubernetes 允许我们分配以下限制的名称:

  • 最多 253 个字符

  • 小写字母和数字字符

  • 可能包含特殊字符,但只能是破折号(-)和点(.)。

如何做...

为 Pod 分配名称,请按照以下步骤操作:

  1. 以下示例是 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
  1. 您可以使用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
  1. 另一方面,以下示例包含两个容器,但分配相同的名称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样式的标识符。它还允许重复。因此,如果您想分配类似以下信息的内容,请改用标签:

  • 环境(例如:暂存,生产)

  • 版本(例如:v1.2)

  • 应用程序角色(例如:前端,工作人员)

此外,Kubernetes 还支持具有不同命名空间的名称。这意味着您可以在不同的命名空间中使用相同的名称(例如:nginx)。因此,如果您只想分配一个应用程序名称,请改用命名空间。

另请参阅

本章节描述了如何分配和查找对象的名称。这只是一种基本方法,但 Kubernetes 还有更强大的命名工具,如命名空间和选择器,用于管理集群:

  • 与 Pod 一起工作

  • 部署 API

  • 与服务一起工作

  • 与命名空间一起工作

  • 使用标签和选择器

使用命名空间

在 Kubernetes 集群中,资源的名称是命名空间内的唯一标识符。使用 Kubernetes 命名空间可以将同一集群中的不同环境的用户空间分开。它使您能够创建一个隔离的环境,并将资源分区到不同的项目和团队。您可以将命名空间视为虚拟集群。Pod、服务和部署都包含在某个命名空间中。一些低级资源,如节点和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 系统的用户呈现一些公共配置。在启动系统时,minikubekubeadm都会创建它。

命名空间的名称必须是 DNS 标签,并遵循以下规则:

  • 最多 63 个字符

  • 匹配正则表达式 a-z0-9

如何做...

在本节中,我们将演示如何创建命名空间,更改默认命名空间和删除命名空间。

创建命名空间

要创建一个命名空间,请按照以下步骤:

  1. 在确定我们想要的命名空间名称后,让我们使用配置文件创建它:
$ 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
  1. 您现在可以看到我们有一个名为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
  1. 在尝试检查新创建的资源时,我们无法像通常那样轻松找到它们:
$ kubectl get deployment
No resources found.
  1. 相反,部署显示了与命名空间相关的标志:
// 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中的当前上下文切换到另一个上下文来更改默认命名空间:

  1. 首先,我们可以使用子命令config检查当前上下文:
// check the current context in kubeconfig
$ kubectl config current-context
kubernetes-admin@kubernetes

在检查当前上下文时,您可能会对输出感到陌生。上一个当前上下文的值是由kubeadm定义和创建的。如果您使用minikube作为 Kubernetes 系统管理工具,您可能会在屏幕上看到minikube

  1. 无论您从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.
  1. 上述命令是基于kubeadm管理的 Kubernetes;您可以为minikube发出类似的命令,其中包括kubeconfig中默认集群和用户的名称:
// for minikube environemt
$ kubectl config set-context my-context --namespace=my-namespace --cluster=minikube --user=minikube
  1. 接下来,检查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配置时,在上下文部分,您可以找到一个与我们定义的完全相同的上下文,并且还将我们新创建的命名空间。

  1. 执行以下命令以切换到使用新的上下文:
$ kubectl config use-context my-context
Switched to context "my-context".
// check current context
$ kubectl config current-context
my-context

现在当前的上下文是我们定制的一个,它与命名空间my-namespace一起。

  1. 由于默认的命名空间已更改为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的上下文是一个客户端概念,只能由特定用户控制,并且可以更轻松地与命名空间和集群一起工作。另一方面,命名空间是服务器端的概念,在集群中用于资源隔离,并且可以在客户端之间共享。

还有更多...

我们不仅利用命名空间来分隔我们的资源,还可以实现更精细的计算资源配置。通过限制命名空间的计算能力的使用量,系统管理员可以避免客户端创建过多资源并使服务器超载。

创建一个 LimitRange

要设置每个命名空间的资源限制,应该在 Kubernetes API 服务器中添加准入控制器LimitRanger。如果您的系统管理者是minikubekubeadm,则无需担心此设置。

Kubernetes API 服务器中的准入控制器 准入控制器是 Kubernetes API 服务器中的一个设置,它定义了 API 服务器中的更高级功能。准入控制器中可以设置几个功能。用户可以在启动 API 服务器时通过配置文件或使用 CLI 和标志--admission-control添加这些功能。依赖于minikubekubeadm进行系统管理,它们在准入控制器中有自己的初始设置:

  • kubeadm 中的默认准入控制器InitializersNamespaceLifecycleLimitRangerServiceAccountPersistentVolumeLabelDefaultStorageClassDefaultTolerationSecondsNodeRestrictionResourceQuota

  • minikube 中的默认准入控制器:NamespaceLifecycleLimitRangerServiceAccountDefaultStorageClassResourceQuota

根据您的 API 服务器版本,在官方文档kubernetes.io/docs/admin/admission-controllers/#is-there-a-recommended-set-of-admission-controllers-to-use中有一个推荐的列表。查看更多想法!

一个普通的新 Namespace 在资源配额上没有限制。一开始,我们创建一个 Namespace 并查看其初始设置:

// 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的资源,用于指定 Namespace 的资源限制。以下是创建 Namespace 限制的一个很好的例子:

$ 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

然后,我们将使用2作为 CPU 的max值,200m作为min值来限制 Pod 中的资源,并且使用1Gi作为内存的最大值,6Mi作为最小值。对于容器,CPU 限制在100m - 2之间,内存在3Mi - 1Gi之间。如果设置了最大值,则必须在资源创建时在 Pod/container 规范中指定限制;如果设置了最小值,则必须在 Pod/container 创建时指定请求。LimitRange中的defaultdefaultRequest部分用于指定容器规范中的默认限制和请求。

在文件my-first-limitrange.yaml中,LimitRange中的 CPU 限制的值是什么意思?整数值表示 CPU 的数量;值中的“m”表示毫 CPU,所以200m表示 0.2 CPU(200 * 0.001)。同样,容器的默认 CPU 限制是 0.2 到 0.3,实际限制是 0.1 到 2。

随后,我们在普通的 Namespace 中创建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的详细描述时,您将看到直接附加到 Namespace 的约束。不需要添加LimitRange。现在,此 Namespace 中创建的所有 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 资源能够在命名空间下运行。为了实现良好的资源管理,请查看以下的方法:

  • 使用 Pod

  • 部署 API

  • 使用名称

  • 第七章,在 GCP 上构建 Kubernetes中的在节点中设置资源部分

使用标签和选择器

标签是一组键/值对,附加到对象元数据。我们可以使用标签来选择、组织和分组对象,如 Pod、ReplicaSets 和 Services。标签不一定是唯一的。对象可以携带相同的一组标签。

标签选择器用于查询具有以下类型标签的对象:

  • 基于相等性:

  • 使用等于(===)或不等于(!=)运算符

  • 基于集合:

  • 使用innotin运算符

准备工作

在对象中设置标签之前,您应该考虑键和值的有效命名约定。

有效的键应遵循以下规则:

  • 一个可选的前缀名称,用斜杠分隔。

  • 前缀必须是 DNS 子域,由点分隔,不超过 253 个字符。

  • 名称必须少于 63 个字符,由[a-z0-9A-Z]和破折号、下划线和点的组合组成。请注意,如果放在开头和结尾,符号是不合法的。

有效的值应遵循以下规则:

  • 名称必须少于 63 个字符,由[a-z0-9A-Z]和破折号、下划线和点的组合组成。请注意,如果放在开头和结尾,符号是不合法的。

您还应该考虑目的。例如,有两个项目,pilotpoc。此外,这些项目处于不同的环境下,如developproduction。此外,一些包含多个层次,如frontendcachebackend。我们可以将我们的标签键和值组合如下:

  labels:
    project: pilot
    environment: develop
    tier: frontend

如何做...

  1. 让我们尝试使用先前的标签创建几个 Pod,以区分不同的项目、环境和层次,如下所示:
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
  1. 为了方便起见,我们将准备三个包含两个PodsYAML文件,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
  1. 使用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
  1. 运行kubectl describe <Pod name>来检查标签,如下所示。看起来不错,所以让我们使用标签选择器根据不同的条件查询这些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选项指定这些条件来过滤对象的名称;例如:

  • 查询属于试点项目的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
  • 查询属于前端层的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
  • 查询属于前端层和正在开发的环境的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
  • 查询属于前端层而不属于正在开发的环境的Pods
$ kubectl get pods -l "tier=frontend,environment!=develop"
NAME READY STATUS RESTARTS AGE
pilot.prod.nginx 1/1 Running 0 29m

基于集合的标签选择器

使用基于集合的选择器,您可以使用innotin运算符,这类似于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

  • 查询属于试点项目和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
  • 查询属于试点项目和frontendcache层的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
  • 查询属于试点项目而不属于frontendbackend层的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 服务、部署等。但是,某些对象支持基于相等性的选择器,某些对象同时支持两种选择器。因此,让我们看看如何定义它。

还有更多...

标签选择器不仅有助于列出对象,还有助于指定要绑定对象的 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,则可以更轻松地使用 minikube service <Service name> 检查您的 Service,如以下屏幕截图所示。如果您没有使用 minikube,则可以访问任何 Kubernetes 节点和分配的 Service 端口号。对于以下屏幕截图,它将是 <node ip>:31981 或 <node ip>:31820

访问在 minikube 上运行的 Service

使用基于集合的选择器将 Deployment 链接到 ReplicaSet

Deployment 不仅支持基于相等性的选择器,还支持基于集合的选择器,以指定 ReplicaSet。为此,您可以编写 spec.selector.matchExpressions[] 来指定键和 in/notin 运算符。例如,如果您想要指定 project in (poc), environment in (staging), tier notn (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。

以下部分还将使用标签和选择器的概念来利用容器管理:

  • 第三章, 与容器一起玩中的更新实时容器*部分

  • 第七章, 在 GCP 上构建 Kubernetes中的管理 GKE 上的 Kubernetes 集群*部分

第三章:玩转容器

在本章中,我们将涵盖以下主题:

  • 扩展您的容器

  • 更新实时容器

  • 转发容器端口

  • 确保灵活使用您的容器

  • 在 Kubernetes 上提交作业

  • 使用配置文件

介绍

在谈论容器管理时,您需要了解与应用程序包管理(如rpm/dpkg)相比的一些差异,因为您可以在同一台机器上运行多个容器。您还需要关心网络端口冲突。本章介绍了如何使用 Kubernetes 更新、扩展和启动容器应用程序。

扩展您的容器

根据预定义的标准扩展和缩小应用程序或服务的规模是利用最有效的计算资源的常见方式。在 Kubernetes 中,您可以手动扩展和缩小,也可以使用水平 Pod 自动缩放器HPA)进行自动缩放。在本节中,我们将描述如何执行这两个操作。

做好准备

准备以下 YAML 文件,这是一个简单的部署,启动了两个nginx容器。还有一个暴露了 TCP-30080的 NodePort 服务:

# 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 节点(端口范围:3000032767);因此,请确保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!)也支持复制控制器RC)和副本集RS)。但是,部署是部署 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 来监视 Pod 并将指标暴露给 HPA。

首先,Heapster 必须部署在集群中:

如果您正在运行 minikube,请使用minikube addons enable heapster命令在您的集群中启用 heapster。请注意,minikube logs | grep heapster command也可以用来检查 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 Pod:

# 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 刚刚将我们的 Pod 从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 可以在不影响客户端的情况下更新其 Pod。另一种方法称为重新创建,它只是终止所有 Pod,然后创建一个新集合。我们将演示这些解决方案是如何应用在这个配方中。

Docker swarm 中的滚动更新为了实现零停机应用程序更新,在 Docker swarm 中有一个类似的管理功能。在 Docker swarm 中,您可以利用带有--update-delay--update-parallelism--update-failure-action标志的 docker service update 命令。有关 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 和部署之间的关系,请查看第二章中的部署 API部分,深入了解 Kubernetes 概念。

为了说明 Kubernetes 系统中容器的更新,我们将创建一个部署,改变其应用程序的配置,然后检查更新机制如何处理。让我们准备好所有资源:

// 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

在部署上附加一个服务将有助于模拟客户端的真实体验。

如何做...

首先,通过执行以下代码块来查看您刚刚创建的部署及其 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 Name>-<hex decimal hash>的单个 ReplicaSet,与部署一起创建。

接下来,为了验证我们的更新,立即检查当前服务端点的内容:

// 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镜像。

部署更新策略-滚动更新

以下将介绍editset子命令,用于更新部署下的容器:

  1. 首先,让我们用一个新命令更新部署中的 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标志,我们将命令行作为标签保存在修订中。

  1. 编辑部署后,立即使用子命令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

可能会出现几行等待...,如前面的代码所示。它们是标准输出,显示更新的状态。

  1. 对于整个更新过程,请检查部署的详细信息以列出其事件:
// 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 时结束。

  1. 继续检查这次更新的新 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!
  1. 让我们进行另一个更新!这次,使用子命令set来修改 Pod 的特定配置。

  2. 要将新图像设置为部署中某些容器的子命令格式如下:kubectl set image deployment <Deployment name> <Container name>=<image name>

// 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 版本,具有 set 的 CLI 可以分配或更新以下资源:

在设置之后的子命令 操作资源 更新项目
env Pod 环境变量
image Pod 容器镜像
resources Pod 计算资源需求或限制
selector 任何资源 选择器
serviceaccount 任何资源 ServiceAccount
subject RoleBinding 或 ClusterRoleBinding 用户、组或 ServiceAccount
  1. 现在,检查更新是否已完成,以及图像是否已更改:
// 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
...
  1. 您还可以查看 ReplicaSets。应该有另一个负责部署 Pod 的 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 系统记录每次部署的更新:

  1. 我们可以使用子命令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 将其创建视为其第一个版本。但是,您仍然可以在创建部署时记录命令。

  1. 在子命令createrun之后添加标志--record

  2. 有了修订版,我们可以轻松恢复更改,这意味着回滚更新。使用以下命令回滚到以前的修订版:

// 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
  1. 不指定修订号,回滚过程将简单地跳回到以前的版本:
// 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

部署更新策略 - 重新创建

接下来,我们将介绍另一种部署的更新策略,“重新创建”。虽然没有子命令或标志来创建重新创建策略的部署,但用户可以通过覆盖指定配置的默认元素来实现此创建:

// 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 ...

在我们的理解中,“重新创建”模式适用于正在开发中的应用程序。使用“重新创建”,Kubernetes 只是将当前的 ReplicaSet 缩减为零个 Pod,并创建一个新的 ReplicaSet,其中包含完整数量的 Pod。因此,“重新创建”比滚动更新具有更短的总更新时间,因为它只是简单地扩展或缩减 ReplicaSets,一次性完成。由于正在开发的部署不需要关注任何用户体验,因此在更新时出现停机是可以接受的,并且可以享受更快的更新:

// 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 正在扩展以满足所需的数量,而旧的 ReplicaSet 正在缩减以终止其中的所有 Pod。新 ReplicaSet 中的 Pod 附加到原始标签。因此,如果任何服务公开此部署,它将直接接管新创建的 Pod。

有经验的 Kubernetes 用户可能知道,资源 ReplicationController 也可以进行滚动更新。那么,滚动更新在 ReplicationController 和部署之间有什么区别?扩展处理使用 ReplicationController 和客户端(如kubectl)的组合。将创建一个新的 ReplicationController 来替换以前的 ReplicationController。客户端在进行替换时不会感到任何中断,因为服务位于 ReplicationController 之前。然而,开发人员很难回滚到以前的 ReplicationController(它们已被删除),因为没有记录更新历史的内置机制。

此外,如果在滚动更新工作时客户端连接断开,滚动更新可能会失败。最重要的是,部署与 ReplicaSet 是比 ReplicationController 或独立 ReplicaSet 更推荐的部署资源。

在关注部署更新历史时,请注意它并不总是按顺序列出。添加修订的算法可以澄清为以下要点所示:

  • 将最后修订的编号视为N

  • 当新的滚动更新到来时,它将是N+1

  • 回滚到特定修订编号XX将被移除,它将变为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

现在,您可以学习另一种更新部署的方法。

深入研究部署中的滚动更新,我们可能会利用一些参数进行更新:

  • minReadySeconds:在 Pod 被视为就绪后,系统仍然会等待一段时间才能进行下一步。这个时间段是最小就绪秒数,在等待应用程序完成后配置时会很有帮助。

  • maxUnavailable:在更新期间可以不可用的 Pod 的最大数量。该值可以是百分比(默认为 25%)或整数。如果maxSurge的值为0,这意味着 Pod 的数量不能超过所需数量,maxUnavailable的值不能为0

  • maxSurge:在更新期间可以创建的 Pod 的最大数量,超过所需的 ReplicaSet 数量。该值可以是百分比(默认为 25%)或整数。如果maxUnavailable的值为0,这意味着正在提供的 Pod 的数量应始终满足所需的数量,maxSurge的值不能为0

根据代码包中的配置文件my-update-nginx-advanced.yaml,尝试自己玩弄这些参数,并看看您是否能感受到这些想法的运作。

另请参阅

您可以继续学习以下配方,以了解更多关于有效部署 Kubernetes 资源的想法:

  • 扩展您的容器

  • 使用配置文件

  • 在第五章,构建持续交付管道中的将单片式转换为微服务与 Jenkins 集成使用私有 Docker 注册表设置持续交付管道配方

转发容器端口

在之前的章节中,您已经学会了如何使用 Kubernetes 服务来在内部和外部转发容器端口。现在,是时候更进一步,看看它是如何工作的了。

Kubernetes 中有四种网络模型,我们将在以下部分探讨详细信息:

  • 容器到容器的通信

  • Pod 到 Pod 的通信

  • Pod 到服务的通信

  • 外部到内部的通信

准备就绪

在深入研究 Kubernetes 网络之前,让我们学习 Docker 的网络,以了解基本概念。每个容器都有自己的网络命名空间,具有自己的路由表和路由策略。默认情况下,网络桥docker0连接物理网络接口和容器的虚拟网络接口,虚拟网络接口是容器网络命名空间和主机之间的双向电缆。因此,对于单个容器,存在一对虚拟网络接口:容器上的以太网接口(eth0)和主机上的虚拟以太网接口(veth-)。

网络结构可以用以下图像表示:

主机上的容器网络接口什么是网络命名空间?网络命名空间是 Linux 内核提供的技术。有了这个功能,操作系统可以通过将网络功能分离为独立的资源来实现网络虚拟化。每个网络命名空间都有自己的 iptables 设置和网络设备。

如何做...

一个 Pod 包含一个或多个容器,它们在同一主机上运行。每个 Pod 在覆盖网络上都有自己的 IP 地址;Pod 内的所有容器都将彼此视为同一主机上的容器。Pod 内的容器将几乎同时创建、部署和删除。我们将阐述容器、Pod 和服务之间的四种通信模型。

容器到容器的通信

在这种情况下,我们将关注单个 Pod 内容器之间的通信:

  1. 让我们在一个 Pod 中创建两个容器:一个是 nginx web 应用程序,另一个是 CentOS,它会检查本地主机上的端口80
// 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 中有两个容器。

  1. 使用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

  1. 此外,我们可能会发现 Centos 容器可以访问本地的nginx
$ kubectl logs two-container centos | grep "title"
<title>Welcome to nginx!</title>
...
  1. 让我们登录到节点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

现在,我们知道创建的两个容器分别是9e35275934c1e832d294f176。另一方面,还有另一个容器9b3e9caf5149,由 Kubernetes 使用 Docker 镜像gcr.io/google_containers/pause-amd64创建。我们稍后会介绍它。之后,我们可以使用docker inspect命令对容器进行详细检查,并通过添加jq命令(stedolan.github.io/jq/)作为管道,来解析输出信息以仅显示网络设置。

  1. 看看在同一个 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

"pause"容器是做什么的?正如其名字所暗示的,这个容器除了“暂停”之外什么也不做。然而,它保留了网络设置和 Linux 网络命名空间,供 Pod 使用。每当容器关闭和重新启动时,网络配置仍然保持不变,无需重新创建,因为“pause”容器保存了它。您可以在 github.com/kubernetes/kubernetes/tree/master/build/pause 查看它的代码和 Dockerfile 以获取更多信息。

“pause”容器是一个网络容器,当 Pod 创建时会被创建。

它被创建并用于处理 Pod 网络的路由。然后,两个容器将与 pause 共享网络命名空间;这就是为什么它们将彼此视为本地主机。

在 Docker 中创建网络容器在 Docker 中,您可以轻松地将一个容器变成一个网络容器,与另一个容器共享其网络命名空间。使用命令行:$ docker run --network=container:<CONTAINER_ID or CONTAINER_NAME> [other options].然后,您将能够启动一个使用分配的容器的网络命名空间的容器。

Pod 与 Pod 之间的通信

正如前面提到的,Pod 中的容器共享相同的网络命名空间。Pod 是 Kubernetes 中的基本计算单元。Kubernetes 为 Pod 在其世界中分配一个 IP。每个 Pod 都可以在 Kubernetes 网络中使用虚拟 IP 看到其他 Pod。在谈论 Pod 之间的通信时,我们可以分为两种情况:在节点内部通信的 Pod,或者在节点之间通信的 Pod。对于单个节点中的 Pod,由于它们有单独的 IP,它们的传输可以通过桥接来实现,就像 Docker 节点中的容器一样。然而,对于跨节点之间的 Pod 通信,当 Pod 没有主机信息(主机 IP)时,数据包的路由工作将如何进行?

Kubernetes 使用 CNI 来处理集群网络。CNI 是一个用于管理连接容器的框架,用于为容器分配或删除网络资源。虽然 Kubernetes 将 CNI 作为插件,用户可以根据需要选择 CNI 的实现。通常有以下类型的 CNI:

每种类型的 CNI 都有优缺点。前一种类型在可扩展性方面更好,但性能较差,而后一种类型的延迟较短,但需要复杂和定制的设置。一些 CNI 以不同模式覆盖了所有三种类型,例如 Contiv(github.com/contiv/netplugin)。您可以在github.com/containernetworking/cni上检查其规范以获取有关 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 实现大不相同。以下步骤向您展示了如何使用 CNI Calico 启动 minikube:

  1. 我们将这个问题github.com/projectcalico/calico/issues/1013#issuecomment-325689943作为这些构建步骤的参考。

  2. 这里使用的 minikube 是最新版本 0.24.1。

  3. 重新启动您的 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

  4. 使用代码包中的配置文件"minikube-calico.yaml"创建 Calico kubectl create -f minikue-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

现在,我们可以继续并使用wget命令和--spider标志从一个简单的部署busybox检查 Pod 的连接,以验证端点的存在。

// 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

正如您所看到的,在 NeworkPolicy 的规范中,它被配置为应用于具有标签<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

在第二章的使用标签和选择器中,了解标签和选择器的概念。

在这种情况下,您已经学会了如何通过 Pod 选择器创建具有入口限制的 NetworkPolicy。但是,您可能还希望在您的 Pod 上构建其他设置:

  • 出口限制:出口规则可以通过.spec.egress应用,其设置与入口类似。

  • 端口限制:每个入口和出口规则都可以指出要接受或阻止的端口以及端口协议的类型。端口配置可以通过.spec.ingress.ports.spec.egress.ports来应用。

  • 命名空间选择器:我们还可以对特定的命名空间进行限制。例如,系统守护程序的 Pod 可能只允许访问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.yamlnetworkpolicy-deny-all.yaml的代码包。

Pod 与 Service 之间的通信

在正常情况下,Pod 可能会意外停止。然后,Pod 的 IP 可能会更改。当我们为 Pod 或 Deployment 公开端口时,我们创建一个充当代理或负载均衡器的 Kubernetes 服务。Kubernetes 会创建一个虚拟 IP,该 IP 接收来自客户端的请求,并将流量代理到服务中的 Pod。让我们来看看如何做到这一点:

  1. 首先,我们将创建一个 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
  1. 此时,使用子命令describe来检查服务的详细信息:
$ 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:80192.168.80.6:80。此外,由于服务是以NodePort类型创建的,客户端可以在每个 Kubernetes 节点上通过<NODE_IP>:30615访问此服务。根据我们在第二章中使用服务的理解,深入了解 Kubernetes 概念,是 Kubernetes 守护程序kube-proxy帮助在每个节点上维护和更新路由策略。

  1. 继续,在任何 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相关的策略,请按以下步骤检查它们。您屏幕上的输出可能不会按预期顺序列出:

  1. 在带有提到nodeport-svc注释的Chain KUBE-NODEPORTS下找到目标。一个目标将以前缀KUBE-SVC-命名。在前面的输出中,它是名为KUBE-SVC-GFPAJ7EGCNM4QF4H的目标。与其他目标KUBE-MARK-MASQ一起,它们用于将端口30615的流量传递到服务。

  2. Chain KUBE-SERVICES下找到名为KUBE-SVC-XXX的特定目标。在这种情况下,它是名为KUBE-SVC-GFPAJ7EGCNM4QF4H的目标,规定允许来自“任何地方”的流量到达nodeport-svc的端点10.160.245:8080

  3. 在特定的Chain KUBE-SVC-XXX下找到目标。在这种情况下,它是Chain KUBE-SVC-GFPAJ7EGCNM4QF4H。在服务链下,您将根据相应的 Pods 的前缀KUBE-SEP-拥有多个目标。在前面的输出中,它们是KUBE-SEP-TC6HXYYMMLGUSFNZKUBE-SEP-DIS6NYZTQKZ5ALQS

  4. 在特定的Chain KUBE-SEP-YYY下找到目标。在这种情况下,需要查看的两个链是Chain KUBE-SEP-TC6HXYYMMLGUSFNZChain KUBE-SEP-DIS6NYZTQKZ5ALQS。它们各自涵盖两个目标,KUBE-MARK-MASQDNAT,用于“任何地方”到 Pod 端点192.168.80.5:80192.168.80.6:80的进出流量。

这里的一个关键点是,服务目标KUBE-SVC-GFPAJ7EGCNM4QF4H将其集群 IP 暴露给外部世界,将流量分发到链KUBE-SEP-TC6HXYYMMLGUSFNZKUBE-SEP-DIS6NYZTQKZ5ALQS,统计模式为随机概率 0.5。这两个链都有 DNAT 目标,用于改变数据包的目标 IP 为私有子网的 IP,即特定 Pod 的 IP。

外部到内部的通信

要在 Kubernetes 中发布应用程序,我们可以利用 Kubernetes 服务,类型为NodePortLoadBalancer,或 Kubernetes Ingress。对于 NodePort 服务,如前一节介绍的,节点的端口号将与服务配对。就像下图中所示,节点 1 和节点 2 上的端口30361指向服务 A,该服务将流量分发到 Pod1 和一个具有静态概率的 Pod。

负载均衡器服务,正如您可能从第二章中的“使用服务”中学到的那样,包括 NodePort 的配置。此外,负载均衡器服务可以与外部负载均衡器一起工作,为用户提供在云基础架构和 Kubernetes 资源之间集成负载平衡程序的功能,例如设置healthCheckNodePortexternalTrafficPolicy。下图中的服务 B是一个负载均衡器服务。在内部,服务 B服务 A的工作方式相同,依赖于iptables将数据包重定向到 Pod;在外部,云负载均衡器并不了解 Pod 或容器,它只根据节点数量分发流量。无论选择哪个节点来获取请求,它仍然能够将数据包传递给正确的 Pod:

带有 NodePort 和 LoadBalancer 类型的 Kubernetes 服务

使用 Ingress

在 Kubernetes 网络的旅程中,用户会了解到每个 Pod 和 Service 都有自己的私有 IP 和相应的端口来监听请求。在实践中,开发人员可以为内部客户端提供服务的端点,即私有 IP 或 Kubernetes DNS 名称;或者,开发人员可以通过 NodePort 或 LoadBalancer 类型将服务外部暴露。虽然 Service 的端点比 Pod 更稳定,但服务是分开提供的,客户端应该记录这些 IP 而没有太多意义。在本节中,我们将介绍Ingress,这是一个使您的服务作为一个组工作的资源。更重要的是,我们可以在设置 Ingress 规则以识别不同的 URL 时,轻松地将我们的服务联合打包为一个 API 服务器,然后 Ingress 控制器根据规则将请求传递给特定的服务。

在我们尝试 Kubernetes Ingress 之前,我们应该在集群中创建一个 Ingress 控制器。与kube-controller-manager中的其他控制器不同,Ingress 控制器是由自定义实现而不是作为守护程序运行的。在最新的 Kubernetes 版本 1.10 中,nginx Ingress 控制器是最稳定的,通常也支持许多平台。查看部署的官方文档:github.com/kubernetes/ingress-nginx/blob/master/README.md。我们将只在 minikube 上演示我们的示例;请查看以下信息框以设置 Ingress 控制器。

在 minikube 中启用 Ingress 功能 Ingress 在 minikube 中是一个附加功能。按照以下步骤在您的环境中启用此功能:

  1. 检查附加功能 ingress 是否已启用:在终端上输入命令minikube addons list。如果未启用,表示显示为ingress: disabled,则应继续以下步骤。

  2. 启用 ingress:输入命令minikube addons enable ingress,您将看到类似ingress was successfully enabled的输出。

  3. 再次检查附加功能列表,以验证上一步是否有效。我们期望字段 ingress 显示为enabled

下面是一个示例,演示如何使用 Ingress。我们将启动两个部署和它们的服务,以及一个额外的 Ingress 将它们公开为一个联合体。首先,我们将在 Kubernetes 主节点的主机文件中添加一个新的主机名。这是我们演示的一个简单方法。如果您在生产环境中工作,一个常见的用例是将主机名作为记录添加到 DNS 服务器中:

// add a dummy hostname in local host file
$ sudo sh -c "echo `minikube ip` happy.k8s.io >> /etc/hosts"

我们的第一个 Kubernetes 部署和服务将是echoserver,一个显示服务器和请求信息的虚拟服务。对于另一对部署和服务,我们将重用上一节中的 NodePort 服务示例:

$ 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 上监听,主机名为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 中的 Pod,都是不稳定的,并且可以由系统控制器动态地调度到服务上。然而,这样的多层结构增加了网络的复杂性,也会带来潜在的开销。

外部负载均衡器不知道 Pod 的存在;它们只会将工作负载平衡到主机上。没有运行任何 Pod 的主机将把负载重定向到其他主机。这种情况是用户对公平负载均衡的期望所导致的。此外,Pod 可能会意外崩溃,在这种情况下很难进行故障转移和完成请求。为弥补这些缺点,服务网格的理念专注于微服务的网络管理,致力于在像 Kubernetes 这样的编排中提供更可靠和高性能的通信:

简单的服务网格结构

上图说明了服务网格中的主要组件。它们共同工作以实现以下功能:

  • 服务网格入口:使用应用的入口规则来决定哪个服务应该处理传入的请求。它也可以是一个能够检查运行时策略的代理。

  • 服务网格代理:每个节点上的代理不仅指导数据包,还可以作为报告服务整体状态的咨询代理。

  • 服务网格服务发现池:为网格提供中央管理,并控制代理。其责任包括网络能力、认证、故障转移和负载均衡的程序。

尽管诸如 Linkerd(linkerd.io)和 Istio(istio.io)等著名服务网格实现尚不够成熟以用于生产,但服务网格的理念却是不可忽视的。

另请参阅

Kubernetes 基于覆盖网络转发端口。在本章中,我们还将使用 nginx 运行 Pod 和服务。回顾之前的部分将帮助您更多地了解如何操作它。另外,看看以下的食谱:

  • 第一章中的创建覆盖网络在 Kubernetes 中运行您的第一个容器食谱,构建您自己的 Kubernetes 集群

  • 第二章中的使用 Pod使用服务食谱,深入了解 Kubernetes 概念

  • 第五章中的将单片式转换为微服务食谱,构建持续交付管道

确保容器的灵活使用

Pod,在 Kubernetes 中意味着一组容器,也是最小的计算单元。您可能已经了解了上一篇文章中 Pod 的基本用法。Pod 通常由部署管理,并由服务公开;它们在这种情况下作为应用程序工作。

在这个食谱中,我们将讨论两个新功能:DaemonSetsStatefulSets。这两个功能可以管理具有更具体目的的 Pod。

准备就绪

什么是类似守护程序的 Pod有状态的 Pod?Kubernetes 中的常规 Pod 将根据当前节点资源使用情况和您的配置确定并分派到特定的 Kubernetes 节点。

然而,类似守护程序的 Pod将在每个节点上创建。例如,如果您有三个节点,将创建三个类似守护程序的 Pod,并部署到每个节点。每当添加新节点时,守护程序 Pod 将自动部署到新节点。因此,使用节点级监控或日志更正将非常有用。

另一方面,Stateful Pod将固定一些资源,如网络标识符(Pod 名称和 DNS)和持久卷(PV)。这也保证了在部署多个 Pod 时的顺序和在滚动更新期间的顺序。例如,如果您部署了一个名为my-pod的 Pod,并将规模设置为4,那么 Pod 名称将被分配为my-pod-0my-pod-1my-pod-2my-pod-3。不仅 Pod 名称,DNS 和持久卷也会被保留。例如,当my-pod-2由于资源短缺或应用程序崩溃而重新创建时,这些名称和卷将被新的名为my-pod-2的 Pod 接管。这对于一些基于集群的应用程序如 HDFS 和 ElasticSearch 非常有用。

在这个示例中,我们将演示如何使用 DaemonSets 和 StatefulSet;然而,为了更好地理解,应该使用多个 Kubernetes 节点环境。为了做到这一点,minikube 并不理想,所以可以使用 kubeadm/kubespray 来创建多个节点环境。

使用 kubeadm 或 kubespray 来设置 Kubernetes 集群在《构建您自己的 Kubernetes 集群》一章中有描述。

要确认是否有 2 个或更多节点,输入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 镜像容器,每隔半分钟检查一次内存使用情况:

  1. 要将其构建为 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

与 Job 一样,选择器可以被忽略,但它会获取标签的值。我们将始终将 DaemonSet 的重启策略配置为Always,以确保每个节点都有一个正在运行的 Pod。

  1. 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
  1. 在这里,我们有两个运行在不同节点的 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
  1. 使用子命令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。

  1. 让我们再次检查 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
  1. 所以,现在我们通过kubespraykubeadm向集群添加了一个节点,具体取决于你的设置:
$ 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
  1. 几分钟后,没有任何操作,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 分配一个 DNS,使用 Pod 的 IP 地址,例如<Pod IP address>.<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 实现,IP 地址无法保留给 Pods。那我们使用 Kubernetes Service 怎么样?Service 会保留一个 DNS 名称。不幸的是,创建与 Pod 相同数量的 Service 并不现实。在之前的情况下,创建三个绑定到三个 Pods 的 Service。

Kubernetes 针对这种类型的用例有一个解决方案,即使用StatefulSet。它不仅保留了 DNS,还保留了持久卷,以保持与同一 Pod 的绑定。即使 Pod 崩溃,StatefulSet 也保证将相同的 DNS 和持久卷绑定到新的 Pod。请注意,由于当前 Kubernetes 实现,IP 地址不会被保留。

为了演示,使用Hadoop 分布式文件系统HDFS)启动一个 NameNode 和三个 DataNodes。为此,使用来自hub.docker.com/r/uhopper/hadoop/的 Docker 镜像,其中包含 NameNode 和 DataNode 镜像。此外,从gist.github.com/polvi/34ef498a967de563dc4252a7bfb7d582借用 YAML 配置文件namenode.yamldatanode.yaml并稍作修改:

  1. 让我们为namenodedatanode启动一个 Service 和 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-0hdfs-datanode-1hdfs-datanode-2

此外,NameNode 和 DataNode 都有一个配置为无头模式的服务(通过spec.clusterIP: None)。因此,您可以使用 DNS 来访问这些 Pod,格式为<pod-name>.<service-name>.<namespace>.svc.cluster.local。在这种情况下,NameNode 的 DNS 条目可能是hdfs-namenode-0.hdfs-namenode-svc.default.svc.cluster.local

  1. 让我们检查一下 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 
  1. 接下来,登录(运行/bin/bash)到其中一个 DataNodes,使用kubectl exec来解析此 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 的状态。

  1. 为了做到这一点,使用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
  1. 上述结果表明,您的本地机器 TCP 端口60107(您的结果会有所不同)已被转发到 NameNode Pod 的 TCP 端口50070。因此,请使用 Web 浏览器访问http://127.0.0.1:60107/如下:

HDFS Web 控制台显示三个 DataNodes

正如您所看到的,三个 DataNodes 已成功注册到 NameNode。DataNodes 也在使用无头服务,因此相同的命名约定也为 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 恢复

在 Pod 重新创建期间,StatefulSet 的行为与 DaemonSet 不同。在 StatefulSet 管理的 Pod 中,Pod 的名称始终保持一致,分配一个有序的编号,例如hdfs-datanode-0hdfs-datanode-1hdfs-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 丢失了数据,并希望从其他 DataNodes 重新同步。如果数据量更大,可能需要很长时间才能完成重新同步。

幸运的是,StatefulSets 具有保留持久卷的能力,同时替换 Pod。让我们看看 HDFS DataNode 在 Pod 重新创建期间如何保留数据。

还有更多...

具有持久卷的 StatefulSet;它需要一个StorageClass来动态分配卷。因为每个 Pod 都是由 StatefulSets 创建的,它将创建一个具有不同标识符的持久卷索赔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 scale 的效果不佳。为了实现 HDFS NameNode 的高可用性或扩展,请访问 HDFS 联邦文档:hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-hdfs/Federation.html

另请参阅

在这个示例中,我们深入介绍了通过 DaemonSets 和 StatefulSet 管理 Kubernetes Pod。它以一种特定的方式管理 Pod,比如每个节点一个 Pod 和一致的 Pod 名称。当 Deployments/ReplicaSets 无法满足您的应用程序用例时,它非常有用。有关更多信息,请参考以下内容:

  • 在第二章的Working with Pods示例中,深入了解 Kubernetes 概念

  • 使用配置文件

在 Kubernetes 上提交作业

您的容器应用程序不仅适用于诸如 nginx 之类的守护进程,还适用于一些批处理作业,这些作业在任务完成时最终退出。Kubernetes 支持这种情况;您可以将一个容器提交为一个作业,Kubernetes 将分派到一个适当的节点并执行您的作业。

在这个示例中,我们将讨论两个新功能:作业CronJob。这两个功能可以利用 Pod 来利用你的资源。

准备就绪

自 Kubernetes 版本 1.2 以来,Kubernetes Jobs 已经作为一个稳定的功能引入(apiVersion: batch/v1)。此外,CronJob 是一个 beta 功能(apiVersion: batch/v1beta1),截至 Kubernetes 版本 1.10。

minikube上都能很好地工作,这是在第一章中介绍的,构建您自己的 Kubernetes 集群。因此,这个示例将使用 minikube 版本 0.24.1。

如何做...

在向 Kubernetes 提交作业时,您可以定义三种类型的作业:

  • 单个作业

  • 重复作业

  • 并行作业

Pod 作为单个作业

类似作业的 Pod 适合测试您的容器,可以用于单元测试或集成测试;或者,它可以用于批处理程序:

  1. 在下面的示例中,我们将编写一个 Job 模板来检查 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

请注意,作业中创建的 Pod 的重启策略应设置为NeverOnFailure,因为一旦成功完成作业,作业就会终止。

  1. 现在,您可以使用您的模板创建一个job
$ kubectl create -f job-dpkg.yaml
job.batch "package-check" created
  1. 创建job对象后,可以验证 Pod 和 Job 的状态:
$ kubectl get jobs
NAME            DESIRED   SUCCESSFUL   AGE
package-check   1         1            26s
  1. 这个结果表明作业已经完成,在26秒内执行(通过SUCCESSFUL = 1)。在这种情况下,Pod 已经消失:
$ kubectl get pods
No resources found, use --show-all to see completed objects.
  1. 正如您所看到的,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 将在进程结束时很快停止。

  1. 使用子命令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
.
.
.
  1. 请继续使用子命令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
.
.
.
  1. 稍后,要删除您刚刚创建的job,请使用名称将其删除。这也会删除已完成的 Pod:
$ kubectl delete jobs package-check
job.batch "package-check" deleted

$ kubectl get pods --show-all
No resources found.

创建一个可重复的作业

用户还可以决定在单个作业中应完成的任务数量。这有助于解决一些随机和抽样问题。让我们在前面示例中的相同模板上尝试一下:

  1. 添加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
  1. 创建此作业后,使用子命令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

如您所见,创建了三个 Pod 来完成此作业。如果您需要在特定时间重复运行程序,这将非常有用。但是,正如您从前面结果的Age列中注意到的那样,这些 Pod 是顺序运行的,一个接一个。这意味着第二个作业在第一个作业完成后开始,第三个作业在第二个作业完成后开始。

创建一个并行作业

如果您的批处理作业没有状态或作业之间的依赖关系,您可以考虑并行提交作业。与spec.completions参数类似,作业模板具有一个spec.parallelism参数,用于指定要并行运行多少个作业:

  1. 重复使用一个可重复的作业,但更改它以指定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
  1. 结果类似于spec.completions=3,这使得3个 Pod 来运行您的应用程序:
$ 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
  1. 但是,如果您通过kubectl describe命令看到一个Age列,表示3个 Pod 同时运行:
$ 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 可以分派到可用节点来运行您的应用程序,并且可以轻松扩展您的作业。如果您想要运行类似工作程序的东西,将一堆 Pod 分发到不同的节点上,这将非常有用。

使用 CronJob 调度运行作业

如果您熟悉UNIX CronJobJava Quartzwww.quartz-scheduler.org),Kubernetes CronJob 是一个非常直接的工具,您可以定义特定的时间来重复运行您的 Kubernetes 作业。

调度格式非常简单;它指定了以下五个项目:

  • 分钟(0 - 59)

  • 小时(0 - 23)

  • 月份(1 - 12)

  • 月份(1 - 12)

  • 星期几(0:星期日 - 6:星期六)

例如,如果您想要在每年的 11 月 12 日上午 9:00 运行您的作业,以向我发送生日祝福:-),调度格式可以是0 9 12 11 *

您还可以使用斜杠(/)来指定步长值;对于前面作业示例的每 5 分钟运行一次间隔,将具有以下调度格式:*/5 * * * *

此外,还有一个可选参数spec.concurrencyPolicy,您可以指定一个行为,如果前一个作业没有完成,但下一个作业的计划接近,以确定下一个作业的运行方式。您可以设置为:

  • Allow:允许执行下一个作业

  • Forbid:跳过执行下一个作业

  • Replace:删除当前作业,然后执行下一个作业

如果设置为Allow,可能会在 Kubernetes 集群中积累一些未完成的作业潜在风险。因此,在测试阶段,您应该设置为ForbidReplace以监视作业的执行和完成:

$ 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>

几分钟后,作业将按照您期望的时间触发——在本例中,每 5 分钟。然后,您可以通过kubectl get jobskubectl get pods -a命令查看作业条目,如下所示:

//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 将创建一个新的作业条目,并且相关的 Pod 也将继续被创建。这将影响 Kubernetes 资源的消耗。因此,默认情况下,CronJob 将保留最多3个成功的作业(通过spec.successfulJobsHistoryLimit)和一个失败的作业(通过spec.failedJobsHistoryLimit)。您可以根据自己的需求更改这些参数。

总的来说,CronJob 补充允许作业根据所需的时间自动运行在您的应用程序中。您可以利用 CronJob 来运行一些报告生成作业,每日或每周批处理作业等。

它是如何工作的...

尽管作业和 CronJob 是 Pod 的特殊实用程序,但 Kubernetes 系统在它们和 Pod 之间有不同的管理系统。

对于作业,其选择器不能指向现有的 Pod。将由部署/副本集控制的 Pod 作为作业是一个不好的主意。部署/副本集有一个期望数量的正在运行的 Pod,这与作业的理想情况相违背:Pod 应该在完成任务后被删除。部署/副本集中的 Pod 将无法达到结束状态。

另请参阅

在这个示例中,我们执行了作业和 CronJob,展示了 Kubernetes Pod 具有完成状态的另一种用法。即使 Pod 完成了,Kubernetes 也可以保留日志和 Pod 对象,以便您可以轻松检索结果。有关更多信息,请参考:

  • 第二章中的使用 Pod示例,深入了解 Kubernetes 概念

  • 使用配置文件

使用配置文件

Kubernetes 支持两种不同的文件格式,YAMLJSON。每种格式都可以描述 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的第一个实例后,可以生成模式。缓存的模式将根据您运行的 API 服务器版本存储在.kube/cache/discovery/<SERVICE_IP>_<PORT>下:

// 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 Deployment API 文档的说明

点击 spec DeploymentSpec

跟踪 Deployment 容器配置的解决方案

  • 现在你明白了!

  • 点击 containers Container array

  • 点击 template PodTemplateSpec

  • 以下是前面示例的解决方案:

点击 spec PodSpec

但是,我们如何知道在前面图像的标记位置设置容器部分的详细信息?在对象描述的字段部分,有两个值。第一个,如 apiVersion,表示名称,第二个,如 string,是类型。类型可以是整数、字符串、数组或其他资源对象。因此,要搜索部署的容器配置,我们需要了解对象的层次结构。首先,根据网页上的示例配置文件,到容器的对象层是spec.template.spec.containers.。因此,首先点击 Deployment 的字段下的超链接 spec DeploymentSpec,这是资源对象的类型,并进行分层搜索。最后,您可以在此页面上找到列出的详细信息:kubernetes.io/docs/reference/generated/kubernetes-api/v1.10/#container-v1-core

  • 仔细查看容器配置的定义。以下是一些您应该注意的常见描述:

  • 可选或不可选:一些项目被标记为可选,这意味着不是必需的,并且可以作为默认值应用,或者如果你不指定它,就不设置它。

  • 无法更新:如果项目被指示为无法更新,则在创建资源时会被固定。你需要重新创建一个新的,而不是更新它。

  • 只读:一些项目被标记为只读,比如 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=ture。然后,通过访问端点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 String 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 有一些详细的选项来定义容器和组件。更多细节,请参考以下食谱,将描述如何定义 Pods、Deployments 和 Services:

  • 在第二章的使用 Pods部署 API使用服务食谱,Kubernetes 概念概述中。

第四章:构建高可用性集群

在本章中,我们将涵盖以下配方:

  • 集群化 etcd

  • 构建多个主节点

介绍

避免单点故障是一个我们需要时刻牢记的概念。在本章中,您将学习如何在 Kubernetes 中构建具有高可用性的组件。我们还将介绍构建一个三节点 etcd 集群和多节点主节点的步骤。

集群化 etcd

etcd 在 Kubernetes 中存储网络信息和状态。任何数据丢失都可能是至关重要的。在生产环境中强烈建议对 etcd 进行集群化。etcd 支持集群化;N 个成员的集群可以容忍最多(N-1)/2 个故障。通常有三种创建 etcd 集群的机制。它们如下:

  • 静态

  • etcd 发现

  • DNS 发现

如果我们在启动之前已经为所有 etcd 成员进行了配置,静态是一种简单的引导 etcd 集群的方法。然而,如果我们使用现有的 etcd 集群来引导新成员,那么发现方法就会发挥作用。发现服务使用现有集群来引导自身。它允许 etcd 集群中的新成员找到其他现有成员。在这个配方中,我们将讨论如何通过静态和 etcd 发现手动引导 etcd 集群。

我们在第一章中学习了如何使用 kubeadm 和 kubespray,构建您自己的 Kubernetes 集群。在撰写本文时,kubeadm 中的 HA 工作仍在进行中。官方文档建议定期备份您的 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 中,引入了租约的概念。租约可以附加多个键;当租约到期时,它将删除所有附加的键。对于观察者,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 服务需要端口 23794001用于旧版本)用于 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

此脚本将在/etc/etcd文件夹下放置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 集群:

  1. 首先,我们需要有一个具有三节点配置的现有集群。幸运的是,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
  1. 然后我们可以使用 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
  1. 让我们仔细看一下 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

我们可以看到第一个节点等待其他两个成员加入,并将成员添加到集群中,在第 2 个任期的选举中成为了领导者:

  1. 如果您检查其他服务器的日志,您可能会发现一些成员投票给了当前的领导者:
// in node 2
2018-02-10 04:58:37.118601 I | raft: raft.node: 6645fe871c820573 elected leader f60c98e749d41d1b at term 2
  1. 我们还可以使用成员列表来检查当前的领导者:
# 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
  1. 然后我们可以确认当前的领导者是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
  1. 如果我们通过etcdctl命令删除当前的领导者:
# etcdctl member remove f60c98e749d41d1b
  1. 我们可能会发现当前的领导者已经改变了:
# 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 供我们使用。我们可以利用它来检查集群的统计信息:

  1. 例如,使用/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 来帮助您动态地引导集群。我们所需要做的就是启动一个 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;您可以在每个 instanceGroup 下的 kops 配置文件中添加一个版本键,例如 version: 3.3.0

另请参阅

  • 通过使用 kubespray 在 Linux 上设置 Kubernetes 集群构建您自己的 Kubernetes 集群中的第一章。

  • 本章的构建多个主节点部分

  • 第六章,在 AWS 上构建 Kubernetes

  • 在第九章中处理 etcd 日志日志和监控

构建多个主节点

主节点在 Kubernetes 系统中充当内核组件。其职责包括以下内容:

  1. 从 etcd 服务器推送和拉取信息

  2. 作为请求的门户

  3. 将任务分配给节点

  4. 监控正在运行的任务

三个主要的守护程序使主节点能够完成前面的任务;以下图表显示了上述要点的活动:

Kubernetes 主节点与其他组件之间的交互

正如你所看到的,主节点是工作节点和客户端之间的通信者。因此,如果主节点崩溃,这将是一个问题。多主 Kubernetes 系统不仅具有容错能力,而且负载均衡。如果其中一个崩溃,也不会有问题,因为其他主节点仍然会处理工作。我们将这种基础设施设计称为高可用性,缩写为 HA。为了支持 HA 结构,将不再只有一个 API 服务器用于访问数据存储和处理请求。在分离的主节点中有几个 API 服务器将有助于同时解决任务并缩短响应时间。

准备就绪

关于构建多主系统,有一些简要的想法你应该了解:

  • 在主节点前添加一个负载均衡器服务器。负载均衡器将成为节点和客户端访问的新端点。

  • 每个主节点都运行自己的 API 服务器。

  • 系统中只有一个调度程序和一个控制器管理器有资格工作,这可以避免不同守护程序之间的冲突方向,同时管理容器。为了实现这一设置,我们在调度程序和控制器管理器中启用了--leader-elect标志。只有获得租约的人才能担任职务。

在这个配方中,我们将通过kubeadm构建一个双主系统,它具有类似的方法,同时可以扩展更多的主节点。用户也可以使用其他工具来构建高可用的 Kubernetes 集群。我们的目标是阐明一般概念。

在开始之前,除了主节点,您还应该在系统中准备其他必要的组件:

  • 两台 Linux 主机,稍后将设置为主节点。这些机器应配置为 kubeadm 主节点。请参考第一章中的kubeadm 配方在 Linux 上设置 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

你可能会收到“不支持交换”的错误消息。在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.crtapiserver.key文件。这是因为这些文件应该根据第二个主节点的主机名和 IP 生成,但共享的客户端证书ca.crt也参与了生成过程:

$ sudo rm /etc/kubernetes/pki/apiserver.*

接下来,在执行主节点初始化命令之前,请更改第二个主节点的配置文件中的 API 广告地址。它应该是第二个主节点的 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

它是如何工作的...

验证我们的 HA 集群,请查看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 时,您可能会好奇为什么控制器管理器和调度器在两个主节点上都在运行。在 HA 集群中不是只有一个吗?

正如我们在前一节中了解的那样,我们应该避免在 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 会创建一个新的,因为它保证会启动/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标志定义。检查/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 进行单主安装的基本概念。请参考这里提到的相关食谱,以了解如何自动构建多主系统的想法:

  • 在 Linux 上通过 kubeadm 设置 Kubernetes 集群在第一章中,构建您自己的 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,您将被重定向到您的仪表板页面如下:

登录到 Docker hub 后

如何做...

准备微服务和前端 WebUI 作为 Docker 镜像。然后,使用 Kubernetes 复制控制器和服务部署它们。

微服务

通过以下步骤构建一个提供简单数学函数的微服务:

  1. 这是使用 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') 
  1. 准备一个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"]
  1. 然后,使用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
  1. 然后,使用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
  1. 最后,使用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
  1. 访问 Docker hub 后,您可以在存储库中看到您的微服务:

您的微服务 Docker 镜像在 Docker hub 上

前端 WebUI

构建使用前述微服务的 WebUI 的步骤:

  1. 这是一个简单的前端 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 服务名称和端口号作为环境变量提供给其他 pod。因此,环境变量的名称和 Kubernetes 服务名称必须一致。在这种情况下,my-calc服务名称必须是my-calc-service

  1. 前端 WebUI 使用Flask HTML 模板;它类似于 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>
  1. Dockerfile与微服务my-calc完全相同。因此,最终的文件结构将如下所示。请注意,index.html是一个 jinja2 模板文件;因此,将其放在/templates目录下:
/Dockerfile /entry.py /templates/index.html
  1. 然后,按以下方式构建 Docker 镜像并推送到 Docker hub:

为了将您的镜像推送到 Docker hub,您需要使用Docker login命令登录。这只需要一次;系统会检查~/.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
  1. 访问 Docker hub 后,您可以在存储库中看到您的 WebUI 应用程序:

Docker Hub 上的微服务和前端 WebUI 镜像

它是如何工作的...

让我们准备两个 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)以便从外部 Web 浏览器访问:

$ 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

让我们尝试使用 Web 浏览器访问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 

此外,如果需要修复一些错误,例如,如果前端需要验证

输入参数以检查它是数字还是字符串(是的,如果您输入字符串并提交,它将显示一个错误!),这不会影响构建和部署周期。

然后提交,它将显示一个错误!),这不会影响构建和部署周期

微服务:

前端和微服务 pod 和服务

此外,如果您想添加另一个微服务,例如减法微服务,您可能需要创建另一个 Docker 映像,并与另一个部署和服务一起部署,以便它独立于当前的微服务。然后,您可以不断积累自己的微服务生态系统以便在另一个应用程序中重用。

使用私有 Docker 注册表

一旦您开始通过 Docker 构建微服务应用程序,您将需要一个 Docker 注册表来存放您的容器映像。Docker hub 为您提供免费的公共存储库,但在某些情况下,您可能希望由于业务需求或组织政策而将您的映像设为私有。

Docker hub 提供私有存储库,只允许经过身份验证的用户推送和拉取您的图像,并且对其他用户不可见。但是,免费计划只有一个配额(存储库)。您可以支付以增加私有存储库的数量,但是如果您采用微服务架构,您将需要大量的私有存储库:

Docker hub 私有存储库价格表

Docker hub 的付费计划是设置私有注册表的最简单方法,但是还有其他一些方法可以在您的网络内设置无限的 Docker 镜像配额的私有 Docker 注册表。此外,您还可以使用其他云提供的注册表服务来管理您的私有注册表。

准备工作

在这个配方中,我们将向您展示三种不同的方法来设置您自己的私有注册表:

当使用 Kubernetes 设置私有注册表时,您可以在私有或公共云上使用自己的 Kubernetes 集群,这样您就可以完全控制并充分利用您的物理资源。

另一方面,当使用公共云提供的服务,如 AWS 或 GCP 时,您可以摆脱服务器和存储的管理。无论您需要什么,这些公共云都为您提供弹性资源。我们只需要设置 Kubernetes 的凭据并让节点知道。以下配方将介绍这三种不同的选项。

使用 Kubernetes 运行 Docker 注册表服务器

如果您想使用 Kubernetes 启动一个私有注册表服务器,您需要自己的 Kubernetes 集群。在探索本书时,您将设置自己的 Kubernetes。如果您还没有这样做,请阅读第一章,构建您自己的 Kubernetes 集群,选择最简单的方法。

请注意,Docker 注册表将存储一些 Docker 镜像。您必须有一个PersistentVolume通过 Kubernetes 管理您的存储。此外,我们应该期望多个 pod 将读取和写入相同的PersistentVolume,因为可扩展性。因此,您必须具有PersistentVolumeReadWriteManyRWX)访问模式,例如 GlusterFS 或 NFS。

PersistentVolume的详细信息在第二章的使用卷部分中有描述,深入了解 Kubernetes 概念。让我们创建一个使用 NFS 和名称pvnfs01来分配100GB 的PersistentVolume

//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(副本:一个)。或者,您可以使用 AWS S3 或 GCP PD 作为私有注册表后端存储;请访问docs.docker.com/registry/configuration/了解如何为您的注册表配置后端存储。

接下来,创建PersistentVolumeClaim,将 NFSPersistentVolume和 pod 配置解耦。让我们创建一个名为pvc-1PersistentVolumeClaim。确保accessModesReadWriteMany,并且创建后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。您将需要创建一个带有ACCESS KEY IDSECRET ACCESS KEY的 IAM 用户,并关联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 证书:

  1. 创建一个secrets目录:
$ mkdir secrets
  1. 运行openssl命令指定选项在secrets目录下生成证书(domain.crt)和私钥(domain.key)。请注意,您可以输入.跳过输入位置和电子邮件信息:
$ 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 []:.
  1. 检查证书和私钥是否都生成在secrets目录下:
$ ls secrets/
domain.crt domain.key

创建 HTTP 密钥

关于 HTTP 秘钥,私有注册表实例在启动时默认会随机生成。然而,如果运行多个 pod,则可能会出现每个 pod 具有不同 HTTP 秘钥的问题,这会导致 Docker 客户端推送或拉取镜像时出错。因此,我们明确声明所有 pod 将使用相同的 HTTP 秘钥,通过以下步骤:

  1. 使用openssl命令在secrets目录下创建一个http.secret文件:
//create 8 byte random HEX string by OpenSSL 
$ openssl rand -hex -out secrets/http.secret 8
  1. 检查secrets目录,现在有三个文件:
$ ls secrets/
domain.crt domain.key http.secret

创建 HTTP 基本身份验证文件

最后,关于 HTTP 基本身份验证文件,如果您设置了私有注册表,则在与 Docker 注册表交互时需要进行身份验证。在推送和拉取镜像时,您将需要执行docker login以获取令牌。为了创建一个 HTTP 基本身份验证文件,使用 Apache2 提供的htpasswd命令是最简单的。通过以下步骤创建 HTTP 基本身份验证文件:

  1. 使用 Apache2 Docker 镜像(httpd)运行htpasswd命令,并带有bcrypt-B)选项,在secrets目录下生成一个基本身份验证文件(registry_passwd):
//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 
  1. 检查secrets目录,现在有四个文件:
$ ls secrets/ domain.crt  domain.key  http.secret  registry_passwd

创建一个用于存储安全文件的 Kubernetes 秘钥

有四个文件。我们使用Kubernetes Secret,以便所有 pod 可以通过环境变量或挂载卷访问它并作为文件访问。有关秘钥的更多细节,请参阅第二章中的使用秘钥部分,深入了解 Kubernetes 概念。您可以使用kubectl命令通过以下步骤加载这四个文件并存储到 Kubernetes 秘钥中:

  1. 使用kubectl create命令并带有--from-file参数来指定 secrets 目录:
$ kubectl create secret generic registry-secrets --from-file secrets/
secret "registry-secrets" created
  1. 通过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 YAML 文件进行部署,用于创建一个注册表,并在其中包含前述变量,以便注册表 pod 可以使用它们。现在我们有PersistentVolumeClaim作为pvc-1,它提供容器映像存储,并通过 Secret registry-secrets挂载 SSL 证书文件(domain.crtdomain.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 云注册表需要特别考虑。让我们为私有注册表、Amazon ECR 和 Google 云注册表配置凭据。

从您的私有注册表中推送和拉取映像

现在您可以将容器映像推送到您的私有注册表。因为我们已经设置了 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

如果您真的想要通过从 ECR 存储库中拉取图像来使用 Kubernetes 集群,那么需要面临的挑战是您需要每 12 小时更新一次 ECR 令牌。也许您可以使用定时作业或采用一些自动化工具来完成这项工作。

有关更多详细信息,请访问 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 秘钥中。为此,请执行以下步骤:

  1. 创建一个 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  
  1. 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 
  1. 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] 
  1. 使用docker login来检查密钥文件是否有效:
//note that username must be _json_key 
$ cat container-sa.json | docker login --username **_json_key** --password-stdin gcr.io Login Succeeded
  1. 使用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 秘钥。

  1. 创建一个 Kubernetes 秘钥(my-gcr-secret)来指定_json_keycontainer-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
  1. 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

恭喜!现在你可以使用由 GCP 完全管理的 Google 容器注册表作为你的私有注册表。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 主 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/,无法处理此教程。在启动minikube后,用coredns插件替换kube-dns插件可能会解决此问题:

$ minikube addons disable kube-dns

$ minikube addons enable coredns

如何操作...

在 Jenkins 设置中有两个重要的部分要经历:

  1. Jenkins 需要运行docker命令来构建您的应用程序以组成容器镜像

  2. Jenkins 需要与 Kubernetes 主节点通信以控制部署

为了实现第 1 步,有一个需要类似Docker-in-Docker(dind)的棘手部分。这是因为 Jenkins 是由 Kubernetes 作为一个 pod(Docker 容器)运行的,而且 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  

结果,如果一个 pod 使用jenkins-sa服务账户启动,这个 pod 有权限控制 Kubernetes 集群,因为有cluster-admin ClusterRole

它应该创建一个具有 Jenkins 使用最小权限的自定义ClusterRole。但这个配方是专注于 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.sockjenkins-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 的初始配置:

  1. 访问 http://127.0.0.1:58080 的 Jenkins WebUI;它会要求您输入 initialAdminPassword

  2. 使用 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
  1. 您将看到 Jenkins 的顶部页面。然后点击管理 Jenkins,然后配置系统:

导航到 Jenkins 配置

  1. 滚动到底部,找到云部分。点击“添加新的云”以选择 Docker:

添加 Docker 设置

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

在 Jenkins 上配置 Docker

使用 Jenkins 构建 Docker 镜像

让我们配置一个 Jenkins 作业来构建一个示例微服务应用程序,该应用程序在上一个配方中介绍过(my-calc)。执行以下步骤来配置和构建 Docker 镜像:

  1. 在左侧导航中,点击新项目:

导航到创建新项目

  1. 输入所需的项目名称(例如:my-calc),选择自由风格项目,然后点击确定:

创建一个新的 Jenkins 作业

  1. 在“源代码管理”选项卡中,选择 Git,并将“存储库 URL”设置为 github.com/kubernetes-cookbook/my-calc.git,或者您可以使用自己的具有 Dockerfile 的存储库:

源代码管理设置

  1. 在“构建环境”选项卡中,点击“添加构建步骤”以添加“构建/发布 Docker 镜像”:

构建环境设置

  1. 在“构建/发布 Docker 镜像”面板中:

  2. Dockerfile 的目录为当前目录(.

  3. 在我们设置的云中选择 my-docker

  4. 将镜像设置为您的 Docker 存储库,但附加 :${BUILD_NUMBER}(例如:hidetosaito/my-calc:${BUILD_NUMBER}

  5. 启用推送镜像

  6. 点击“添加”以添加您的 Docker hub ID 凭据

  7. 然后,点击保存:

Docker 构建/发布设置

  1. 最后,您可以点击“立即构建”来触发构建;为了测试目的,您可以点击五次来看看它是如何工作的:

触发构建

  1. 请注意,您可以看到一个控制台,它知道执行 Docker 构建和推送:

显示构建日志

  1. 访问您的 Docker hub 存储库;它已被推送了五次(因为点击了五次构建):

Docker hub 存储库

就是这样!您可以实现持续集成来构建 Docker 镜像,这样当您在 GitHub 中更新源代码时,您可以持续构建并将最新的镜像推送到 Jenkins 的 Docker hub 存储库中。

部署最新的容器镜像到 Kubernetes

每次构建后,Jenkins 都会在 CI 过程结束时将您的容器镜像推送到 Docker hub 存储库。接下来,更新 Jenkins 作业配置以使用最新的镜像部署到 Kubernetes,具体步骤如下:

  1. 第一次,我们通过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
  1. 打开 Jenkins 作业配置;在“构建”选项卡上,在 Docker 构建设置之后,点击“添加构建步骤”,然后选择“执行 shell”:

添加构建步骤

  1. 将此 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

  1. 触发新的构建;您可以看到在 Docker 推送之后,它运行了前面的脚本:

Kubernetes 滚动结果

现在您可以将持续集成扩展到持续交付!您可以扩展以添加单元测试或集成测试,并将回滚机制添加到上述脚本中,以使您的 CI/CD 工作更加强大。

第六章:在 AWS 上构建 Kubernetes

本章涵盖以下内容:

  • 使用亚马逊网络服务

  • 通过 kops 设置 Kubernetes

  • 使用 AWS 作为 Kubernetes 云提供商

  • 通过 kops 在 AWS 上管理 Kubernetes 集群

介绍

根据最近的云原生计算基金会(CNCF)的调查,亚马逊网络服务AWS)是生产级 Kubernetes 系统的主要解决方案(www.cncf.io/blog/2017/12/06/cloud-native-technologies-scaling-production-applications/)。在本章中,您将了解 AWS 的云服务,以及这些服务如何共同工作以提供强大的 Kubernetes 系统。我们还将介绍 kops 的工作原理,这是一个用于 Kubernetes 操作的工具,可以帮助我们管理 Kubernetes 集群。让我们一起探索 AWS 中的 Kubernetes 世界!

使用亚马逊网络服务

亚马逊网络服务(aws.amazon.com)是最受欢迎的公共云服务。它提供虚拟服务器(EC2)、软件定义网络(VPC)、对象存储(S3)等在线服务。这是一个适合建立 Kubernetes 集群的基础设施。我们将探索 AWS 以了解 AWS 的基本功能。

准备工作

首先,您需要注册 AWS。AWS 提供免费套餐,允许您免费使用一定数量的 AWS 资源,有效期为 12 个月。访问aws.amazon.com/free/注册您的信息和信用卡。可能需要 24 小时来验证和激活您的账户。

一旦您的 AWS 账户激活,我们需要创建一个身份和访问管理IAM)用户,通过 API 来控制您的 AWS 基础设施。然后,在您的计算机上安装 AWS CLI。

创建 IAM 用户

执行以下步骤创建 IAM 用户:

  1. 转到 AWS Web 控制台console.aws.amazon.com

  2. 点击 IAM(使用搜索框,这样更容易找到):

访问 IAM 控制台

  1. 点击左侧导航中的用户,然后点击添加用户:

创建 IAM 用户

  1. 输入用户名chap6,然后选择编程访问:

创建 chap6 用户

  1. 选择直接附加现有策略,如下截图所示,然后选择以下策略:
  • AmazonEC2FullAccess

  • AmazonRoute53FullAcccess

  • AmazonS3FullAccess

  • AmazonVPCFullAccess

  • IAMFullAccess

附加必要的策略

  1. 最终,它会生成访问密钥 ID 和秘密访问密钥。将其复制并粘贴到文本编辑器中,或单击下载 .csv 以保存到您的计算机:

下载访问密钥 ID 和秘密访问密钥

在 macOS 上安装 AWS CLI

使用 HomeBrew(brew.sh)将awscli安装到 macOS;这是最简单的方法。在第一章中已经介绍了 HomeBrew,构建您自己的 Kubernetes 集群,同时安装 minikube。

要在 Mac 上通过 HomeBrew 安装 awscli,请执行以下步骤:

  1. 键入以下命令以更新最新的公式:
$ brew update
  1. 指定awscli进行安装:
$ brew install awscli
  1. 使用--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 的最简单方式:

  1. 转到 AWS 命令行界面页面(aws.amazon.com/cli/)。

  2. 根据您的 Windows 操作系统,下载 64 位(s3.amazonaws.com/aws-cli/AWSCLI64.msi)或 32 位(s3.amazonaws.com/aws-cli/AWSCLI32.msi)的 Windows 安装程序。

  3. 启动 AWS CLI 安装程序,然后选择默认选项继续安装:

为 Windows 安装 AWS CLI

  1. 安装完成后,启动命令提示符。然后,键入带有--version选项的aws命令进行验证:

在 Windows 上显示 aws 命令

如何做...

首先,您需要为 awscli 设置 AWS 访问密钥 ID 和 AWS 秘密访问密钥。我们已经为 IAM 用户获得了chap6。我们将使用此用户的访问密钥 ID 和秘密访问密钥。

  1. 启动终端(Windows 的命令提示符),然后使用aws命令设置访问密钥 ID秘密访问密钥。还要将默认区域设置为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]:
  1. 使用以下命令检查chap6 IAM 用户:
$ 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 和两个子网:

  1. 创建一个具有192.168.0.0/16 CIDR 块(IP 范围:192.168.0.0192.168.255.255)的新 VPC。然后,捕获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": []
    }
}
  1. 在 VPC(vpc-69cfbd12)下创建第一个子网,其 CIDR 块为192.168.0.0/24(IP 范围:192.168.0.0192.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": []
    }
}
  1. us-east-1b上创建第二个子网,其 CIDR 块为192.168.1.0/24(IP 范围:192.168.1.0192.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": []
    }
}
  1. 使用以下命令检查 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 |
+------------+------------------+-------------------+---------------+

看起来不错!

互联网网关

访问您的 VPC 网络,您需要有一个从互联网访问它的网关。互联网网关IGW)是将互联网连接到您的 VPC 的网关。

然后,在 VPC 的子网中,您可以设置默认路由是否去往 IGW。如果路由到 IGW,该子网被归类为公共子网。然后,您可以在公共子网上分配全局 IP 地址。

让我们使用以下步骤将第一个子网(192.168.0.0/24)配置为路由到 IGW 的公共子网:

  1. 创建 IGW 并捕获InternetGatewayId
$ aws ec2 create-internet-gateway
{
   "InternetGateway": {
       "Attachments": [],
       "InternetGatewayId": "igw-e50b849d",
       "Tags": []
    }
}
  1. 将 IGW(igw-e50b849d)附加到您的 VPC(vpc-69cfbd12):
$ aws ec2 attach-internet-gateway --vpc-id vpc-69cfbd12 --internet-gateway-id igw-e50b849d
  1. 在 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"
    }
}
  1. 为路由表(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
  1. 将路由表(rtb-a9e791d5)关联到公共子网(subnet-6296863f):
$ aws ec2 associate-route-table --route-table-id rtb-a9e791d5 --subnet-id subnet-6296863f
  1. 在公共子网(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:

  1. NAT-GW 需要全局 IP 地址,因此创建弹性 IPEIP):
$ aws ec2 allocate-address
{
   "PublicIp": "18.232.18.38",
   "AllocationId": "eipalloc-bad28bb3",
   "Domain": "vpc"
}
  1. 在公共子网(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"
    }
}
  1. 创建路由表并捕获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"
    }
}
  1. 将路由表(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
  1. 将路由表(rtb-70f1870c)关联到私有子网(subnet-ce947da9):
$ aws ec2 associate-route-table --route-table-id rtb-70f1870c --subnet-id subnet-ce947da9

安全组

在启动虚拟服务器(EC2)之前,您需要创建一个具有适当安全规则的安全组。现在,我们有两个子网,公共和私有。让我们设置公共子网,使其允许来自互联网的ssh22/tcp)和http80/tcp)。然后,设置私有子网,使其允许来自公共子网的 ssh:

  1. 在 VPC 上为公共子网创建一个安全组(vpc-69cfbd12):
$ aws ec2 create-security-group --vpc-id vpc-69cfbd12 --group-name public --description "public facing host"
{
   "GroupId": "sg-dd8a3f94"
}
  1. 向公共安全组(sg-dd8a3f94)添加 ssh 允许规则:
$ aws ec2 authorize-security-group-ingress --group-id sg-dd8a3f94 --protocol tcp --port 22 --cidr 0.0.0.0/0
  1. 向公共安全组(sg-dd8a3f94)添加http允许规则:
$ aws ec2 authorize-security-group-ingress --group-id sg-dd8a3f94 --protocol tcp --port 80 --cidr 0.0.0.0/0
  1. 在 VPC 上为私有子网创建第二个安全组(vpc-69cfbd12):
$ aws ec2 create-security-group --vpc-id vpc-69cfbd12 --group-name private --description "private subnet host"
{
   "GroupId": "sg-a18c39e8"
}
  1. 向私有安全组(sg-a18c39e8)添加一个ssh允许规则:
$ aws ec2 authorize-security-group-ingress --group-id sg-a18c39e8 --protocol tcp --port 22 --source-group sg-dd8a3f94
  1. 使用以下命令检查安全组列表:
$ 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 实例:

  1. 上传您的 ssh 公钥(假设您的公钥位于~/.ssh/id_rsa.pub):
$ aws ec2 import-key-pair --key-name=chap6-key --public-key-material "`cat ~/.ssh/id_rsa.pub`"
  1. 使用以下参数启动第一个 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
  1. 使用以下参数启动第二个 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
  1. 检查 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   |
+---------------+-----------------+------------------+------------------------+
  1. 从您的计算机通过 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 ~]$
  1. 在公共 EC2 主机上安装并启动 nginx:
[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  ]
  1. 确保您可以从您的机器访问 nginx 服务器(请参见以下截图):

访问公共主机上的 nginx web 服务器

  1. 从公共主机到私有主机的 SSH(您必须使用私有 IP 地址):
$ ssh 192.168.1.218
  1. 确保私有主机可以通过 NAT-GW 执行 yum 更新:
[ec2-user@ip-192-168-1-218 ~]$ sudo yum -y update

恭喜!您可以在 AWS 上设置自己的基础架构,如下图所示,其中包括以下内容:

  • 一个 VPC,CIDR 为192.168.0.0/16

  • IGW

  • NAT-GW

  • 两个子网

  • 公共子网:192.168.0.0/24路由到 IGW

  • 私有子网:192.168.1.0/24 路由到 NAT-GW

  • 两个 EC2 实例(公共和私有)

  • 两个安全组(允许公共 http/ssh 和私有 ssh)

现在,看一下图表:

AWS 组件图

在本节中,您已经学会了如何从头开始使用 AWS。我们已经涵盖了它的基本用途,但在 AWS 上设置 Kubernetes 时,这一点很重要。接下来,我们将探讨如何在 AWS 上设置 Kubernetes。

使用 kops 设置 Kubernetes

什么是 kops?这是 Kubernetes Operation 的缩写(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 主节点的实例大小。 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.
...

几分钟后,该命令会输出之前的日志,显示为您构建的 kops Kubernetes 集群创建了哪些 AWS 服务并为您提供了哪些服务。您甚至可以检查您的 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 服务。

另请参阅

  • 使用亚马逊网络服务玩耍

  • 使用 AWS 作为 Kubernetes 云提供商

  • 通过 kops 在 AWS 上管理 Kubernetes 集群

  • 通过 kubeadm 在 Linux 上设置 Kubernetes 集群[第一章],构建您自己的 Kubernetes 集群

  • 在[第一章]中通过 kubespray 在 Linux 上设置 Kubernetes 集群,构建您自己的 Kubernetes 集群

使用 AWS 作为 Kubernetes 云提供商

从 Kubernetes 1.6 开始,引入了Cloud Controller ManagerCCM),它定义了一组接口,以便不同的云提供商可以在 Kubernetes 发布周期之外演进自己的实现。与云提供商交谈时,您不能忽视最大的参与者:亚马逊网络服务。根据 Cloud Native Computing Foundation 的数据,2017 年,63%的 Kubernetes 工作负载在 AWS 上运行。AWS CloudProvider 支持Elastic Load BalancerELB)作为服务和 Amazon Elastic Block StoreEBS)作为 StorageClass。

在撰写本书时,Amazon Elastic Container Service for Kubernetes (Amazon EKS)处于预览阶段,这是 AWS 中的托管 Kubernetes 服务。理想情况下,它将与 Kubernetes 有更好的集成,例如 Ingress 的Application Load BalancerALB),授权和网络。目前在 AWS 中,VPC 中每个路由表的路由限制为 50;根据 AWS 的官方文档,可以根据请求将其增加到 100。然而,如果路由超过 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 时,我们可以使用两种可能的集成方式:将 ELB 作为LoadBalancer类型的服务,以及将 Amazon Elastic Block Store 作为StorageClass

弹性负载均衡器作为 LoadBalancer 服务

让我们创建一个LoadBalancer服务,并在其下创建 Pods,这是我们在第三章中学到的内容,Playing with Containers

# 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,您可以使用 pip 在 Mac 或 Linux 上安装(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 端点以访问负载均衡器服务

在幕后,AWS CloudProvider 创建了一个 AWS 弹性负载均衡器,并通过我们刚刚定义的服务配置其入口规则和监听器。以下是流量进入 Pod 的图示:

Kubernetes 资源和 AWS 资源的示例,用于 LoadBalancer 类型的服务

外部负载均衡器接收请求并使用轮询算法将其转发到 EC2 实例。对于 Kubernetes,流量通过 NodePort 进入服务,并开始服务与 Pod 之间的通信。有关外部到服务和服务到 Pod 通信的更多信息,您可以参考第三章,使用容器

弹性块存储作为 StorageClass

我们在第二章中学习了关于卷的知识,深入了解 Kubernetes 概念。我们知道PersistentVolumeClaims用于从用户中抽象存储资源。它可以通过StorageClass动态地提供PersistentVolume。在AWS CloudProvider 中,StorageClass 的默认提供者是弹性块存储服务‎(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 Elastic Load Balancer 提供支持。请求将通过 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 的 master 和节点都作为 AWS 中的自动扩展组运行。在 kops 中,这个概念被称为实例组ig),表示集群中相同类型的实例。类似于跨区域的节点,或者每个可用性区域中的 master,我们可以通过 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

在这个例子中,我们将minSizemaxSize都从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 validatekubectl 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

在应用更改之前,我们可以在干跑模式下运行更新集群命令,不带--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

验证干跑消息后,我们可以按以下方式执行更新。在这种情况下,我们将执行滚动更新。

如何知道是否需要进行滚动更新

如果我们在前面的示例中没有运行 kops 滚动更新,那么在运行 kops 验证集群时,kops 将显示验证错误:

验证错误

种类 名称 消息

InstanceGroup master-us-east-1a InstanceGroup master-us-east-1a did not have enough nodes 1 vs 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 nodeskops 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 升级集群命令。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` 

这表明配置已经更新,我们现在需要更新集群。我们首先以干跑模式运行命令,以检查将被修改的内容:

// 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 首先排空一个实例,然后封锁节点。自动扩展组将启动另一个包含更新的用户数据的节点,其中包含了 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)。请参考插件页面以了解更多信息!

另请参阅

  • 在《深入理解 Kubernetes 概念》的第二章中部署 API

  • 在《构建高可用性集群》的第四章中构建多个主节点

  • 在《在 GCP 上构建 Kubernetes》的第七章中管理 GKE 上的 Kubernetes 集群

第七章:在 GCP 上构建 Kubernetes

在本章中,我们将在以下食谱中使用Google Cloud PlatformGCP):

  • 玩转 GCP

  • 通过Google Kubernetes EngineGKE)设置托管的 Kubernetes

  • 探索 GKE 上的 Kubernetes CloudProvider

  • 在 GKE 上管理 Kubernetes 集群

玩转 GCP

GCP 在公共云行业变得越来越受欢迎。它有类似于 AWS 的概念,如 VPC、计算引擎、持久性磁盘、负载均衡和几个托管服务。最有趣的服务是 GKE,这是托管的 Kubernetes 集群。我们将探索如何使用 GCP 和 GKE。

准备工作

要使用 GCP,您需要拥有一个谷歌账号,比如 Gmail(mail.google.com/mail/),很多人已经有了。然后按照以下步骤使用您的谷歌账号注册 GCP:

  1. 转到cloud.google.com网站,然后点击“免费试用”按钮

  2. 使用您的谷歌账号登录谷歌

  3. 注册 GCP 并输入个人信息和结算信息

就是这样!

注册完成后,您将看到 GCP Web 控制台页面。一开始,它可能会要求您创建一个项目;默认名称可能是“My First Project”。您可以保留它,但在本章中我们将创建另一个项目,以帮助您更好地理解。

GCP Web 控制台作为第一步就足够了。但是不建议继续使用 Web 控制台进行 DevOps,因为人工手动输入总会导致人为错误,而且 Google 可能会在将来更改 Web 控制台的设计。

因此,我们将使用 CLI。GCP 提供了一个名为 Cloud SDK 的 CLI 工具(cloud.google.com/sdk/)。因此,让我们创建一个新的 GCP 项目,然后在您的计算机上安装 Cloud SDK。

创建一个 GCP 项目

我们将通过以下步骤从头开始创建一个新项目。这将帮助您了解 GCP 项目的工作原理:

  1. 点击“My First Project”链接转到项目页面:

导航到项目链接

  1. 您可能会看到您自己的项目可供选择,但这次请点击“+”按钮创建一个新项目:

创建一个新项目

  1. 将项目名称输入为Kubernetes Cookbook。然后 GCP 将生成并分配一个项目 ID,如 kubernetes-cookbook-12345。请记住这个项目 ID。

您可能会注意到您的项目 ID 不是 kubernetes-cookbook,就像屏幕截图中显示的 kubernetes-cookbook-194302 一样。即使您尝试点击“编辑”来尝试将其更改为 kubernetes-cookbook,也不允许,因为项目 ID 是所有 GCP 用户的唯一字符串。而我们已经使用了 kubernetes-cookbook 项目 ID。项目名称和项目 ID

  1. 几分钟后,您的项目就准备好使用了。返回顶部横幅上的项目选择页面,然后选择您的 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 计算机上安装:

  1. 在 Windows 上下载 Cloud SDK 安装程序 (dl.google.com/dl/cloudsdk/channels/rapid/GoogleCloudSDKInstaller.exe)。

  2. 运行 Cloud SDK 安装程序。

如果您从未在 Windows 计算机上安装过 Python 解释器,您必须选择“捆绑的 Python”选项:

Windows 的 Cloud SDK 安装程序

  1. 除此之外,使用默认选项继续安装。

  2. 安装完成后,您可以在 Google Cloud SDK 程序组中找到 Google Cloud SDK Shell。单击它以启动 Google Cloud SDK Shell:

Google Cloud SDK 程序组中的 Google Cloud SDK Shell

  1. 键入 gcloud info 以检查您是否可以查看 Cloud SDK 版本:

在 Windows 上运行 gcloud 命令

在 Linux 和 macOS 上安装 Cloud SDK

在 Linux 和 macOS 上安装 Cloud SDK 遵循这里列出的步骤。让我们在您的主目录下安装 Cloud SDK:

  1. 打开终端。

  2. 输入以下命令以下载并运行 Cloud SDK 安装程序:

$ curl https://sdk.cloud.google.com | bash
  1. 它会询问您期望的安装目录。默认情况下,它位于您的主目录下。因此,输入return
Installation directory (this will create a google-cloud-sdk subdirectory) (/Users/saito):
  1. 它会询问是否发送用户使用数据;当它崩溃时,它会发送一些信息。根据您的隐私政策,如果不希望向 Google 发送任何数据,请选择n。否则选择Y以提高它们的质量:
Do you want to help improve the Google Cloud SDK (Y/n)? n
  1. 它会询问是否通过将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]:
  1. 打开另一个终端或输入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
  1. 输入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:

  1. 启动 Google Cloud SDK Shell(Windows)或打开终端(Linux/macOS)。

  2. 输入gcloud init;它会要求您登录您的 Google 帐户。输入y并按回车键:

You must log in to continue. Would you like to log in (Y/n)? y
  1. 它将打开一个网页浏览器,导航到 Google 登录页面;继续使用您的 Google 帐户和 GCP 帐户登录。

  2. 它会询问您是否 Cloud SDK 可以访问您的 Google 帐户信息。点击“允许”按钮。

  3. 回到终端-它会询问您要使用哪个项目。让我们选择您创建的“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
  1. 它会询问您是否要配置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命令重新配置您的设置。

如何做...

我们将通过使用gcloud命令,了解 GCP 的基本功能,以在“Kubernetes Cookbook”项目下设置基础设施。我们将创建这些组件:

  • 一个新的 VPC

  • VPC 中的两个子网(us-central1us-east1

  • 三个防火墙规则(public-sshpublic-httpprivate-ssh

  • 我们将把您的 ssh 公钥添加到项目范围的元数据

总体而言,您的基础设施将如下所示。让我们逐个配置组件:

目标基础设施

创建一个 VPC

GCP 中的 VPC 类似于 AWS,但无需绑定特定区域,也无需设置 CIDR 地址范围。这意味着您可以创建一个覆盖所有区域的 VPC。默认情况下,您的 Kubernetes Cookbook 项目具有一个默认 VPC。

但是,为了更好地理解,让我们按照以下步骤创建一个新的 VPC:

  1. 运行gcloud compute networks命令来创建一个新的 VPC。名称为chap7,子网模式为custom,这意味着子网不会自动创建。因此,我们将在下一步手动添加它:
$ gcloud compute networks create chap7 --subnet-mode=custom
  1. 检查 VPC 列表;您应该有两个 VPC,default VPC 和chap7 VPC:
$ gcloud compute networks list
NAME     SUBNET_MODE  BGP_ROUTING_MODE  IPV4_RANGE  GATEWAY_IPV4 **chap7    CUSTOM       REGIONAL** default  AUTO         REGIONAL

创建子网

让我们按照以下步骤在chap7 VPC(网络)下创建两个子网:

  1. 为了创建一个子网,您必须选择区域。通过输入gcloud compute regions list,您将知道哪些区域对您可用:

显示 GCP 区域列表

  1. 让我们选择us-central1us-east1,按照以下配置在chap7 VPC 下创建两个子网:
子网名称 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
  1. 检查以下命令,以查看子网是否正确配置:
$ 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) 所有 (0.0.0.0/0) public
public-http chap7 http (80/tcp) 所有 (0.0.0.0/0) public
private-ssh chap7 ssh (22/tcp) 具有公共网络标记的主机 private
  1. 创建一个public-ssh规则:
$ gcloud compute firewall-rules create public-ssh --network=chap7 --allow="tcp:22" --source-ranges="0.0.0.0/0" --target-tags="public"
  1. 创建一个public-http规则:
$ gcloud compute firewall-rules create public-http --network=chap7 --allow="tcp:80" --source-ranges="0.0.0.0/0" --target-tags="public"
  1. 创建一个private-ssh规则:
$ gcloud compute firewall-rules create private-ssh --network=chap7 --allow="tcp:22" --source-tags="public" --target-tags="private"
  1. 检查所有防火墙规则:
$ 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的私钥

  1. 使用whoami命令检查您的登录用户名,然后使用gcloud compute config-ssh通过以下命令上传您的密钥:
$ whoami
saito

$ gcloud compute config-ssh --ssh-key-file=~/.ssh/id_rsa
  1. 检查您的 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 产品使用。让我们在您的 VPC 上部署两个 VM 实例,如下图所示,看看它是如何工作的:

最终状态

启动 VM 实例

我们将使用以下配置在us-central1us-east1上启动两个 VM 实例:

VM 实例名称 目标 VPC 区域(参见以下步骤) 目标子网 分配网络标签
chap7-public chap7 us-central1-a chap7-us-central1 public
chap7-private chap7 us-east1-b chap7-us-east1 private
  1. 使用以下命令检查us-central1us-east1中可用的区域:
$ gcloud compute zones list --filter='name:(us-east1,us-central1)'
NAME           REGION       STATUS  NEXT_MAINTENANCE  TURNDOWN_DATE 
**us-east1-b**     us-east1     UP us-east1-c     us-east1     UP us-east1-d     us-east1     UP 
us-central1-c  us-central1  UP **us-central1-a**  us-central1  UP us-central1-f  us-central1  UP us-central1-b  us-central1  UP

因此,让我们选择us-central1-a作为chap7-public,选择us-east1-b作为chap7-private

  1. 输入以下命令创建两个 VM 实例:
$ gcloud compute instances create chap7-public --network=chap7 --subnet=chap7-us-central1 --zone=us-central1-a --tags=public --machine-type=f1-micro
$ gcloud compute instances create chap7-private --network=chap7 --subnet=chap7-us-east1 --zone=us-east1-b --tags=private --machine-type=f1-micro
  1. 通过以下命令检查 VM 实例的外部 IP 地址:
$ gcloud compute instances list
NAME           ZONE           MACHINE_TYPE  PREEMPTIBLE  INTERNAL_IP  EXTERNAL_IP    STATUS 
chap7-public   us-central1-a  f1-micro                   192.168.1.2  **35.224.14.45**   RUNNING 
chap7-private  us-east1-b     f1-micro                   **192.168.2.2**  35.229.95.179  RUNNING
  1. 运行ssh-agent以记住您的 ssh 密钥:
 ssh-add ~/.ssh/id_rsa
  1. 从您的机器通过-A选项(转发身份验证)和使用外部 IP 地址 ssh 到chap7-public

ssh 到公共 VM 实例

  1. 通过内部 IP 地址从chap7-publicchap7-private进行 ssh:

ssh 到私有 VM 实例

  1. 输入exit命令返回到chap7-public主机,然后使用apt-get命令安装nginx

在公共 VM 实例上安装 nginx

  1. 使用以下命令启动nginx
$ sudo systemctl start nginx
  1. 使用您的 Web 浏览器访问chap7-public(通过外部 IP):

访问公共 VM 实例上的 nginx web 服务器

恭喜!您已经完成了设置 GCP VPC、子网和防火墙规则,并启动了 VM 实例!这些都是 Google Compute Engine 的非常基本和常见的用法。您可以登录并在这些机器上安装软件,甚至可以从头开始构建一个 Kubernetes 集群。但是,GCP 还有一个名为 Kubernetes Engine 的托管 Kubernetes 产品。我们将在本章中探讨它。

玩转 Google Kubernetes Engine

Kubernetes 是由谷歌设计的,并在谷歌内部广泛使用多年。Google Cloud Platform 提供托管的 GKE。使用 GKE,我们不需要从头开始构建集群。相反,集群可以按需启动和关闭。

准备就绪

我们可以使用 GCP 控制台中的 Kubernetes Engine 仪表板或 gcloud CLI 来启动和配置集群。使用控制台非常直观和直观。但是,使用 CLI 是使操作可重复或与现有管道集成的更灵活的方式。在这个教程中,我们将介绍如何使用 gcloud 启动和设置 Kubernetes 集群,以及 GCP 中的一些重要概念。

在 GCP 中,一切都与项目相关联。GCP 项目是使用 GCP 服务、计费和权限控制的基本单位。首先,我们需要从 GCP 控制台创建一个项目console.cloud.google.com

在 GCP 中,项目 ID 是全局唯一的。项目正确创建后,我们将看到分配了一个唯一的项目编号。在主页仪表板上,我们将清楚地看到我们使用了多少资源。我们可以从这里设置权限、存储、网络、计费和其他资源。在我们继续之前,我们需要安装 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 以登录并设置您的身份与 gcloud 并创建一个名为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 中,防火墙规则与 VM 实例之间的关联是通过网络标签实现的。

防火墙规则也可以分配给网络中的所有实例或一组具有特定服务帐户的实例(仅入口)。服务帐户是 GCP 中 VM 实例的身份。一个或多个角色可以分配给一个服务帐户,因此它可以访问其他 GCP 资源。这类似于 AWS 实例配置文件。

一个 VM 实例可以有多个网络标签,这意味着可以应用多个网络路由。这张图表展示了标签的工作原理。在下面的图表中,第一个防火墙规则应用于 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 &#124; --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,集群中的 DNS 服务的 KubeDNS,用于 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 概念中,我们学习了如何声明PersistentVolumePersistentVolumeClaim。通过动态配置,您可以定义一组具有不同物理存储后端的StorageClass,并在PersistentVolumePersistentVolumeClaim中使用它们。让我们看看它是如何工作的。

要检查当前默认的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服务类型仅在支持外部负载均衡器的云环境中起作用。这允许外部流量路由到目标 Pod。在 GCP 中,LoadBalancer服务类型将创建一个 TCP 负载均衡器:

  1. 用于允许负载均衡器和节点之间流量的防火墙规则将自动创建:
// 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
  1. 让我们来检查服务。如果负载均衡器仍在进行配置,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
  1. 让我们使用$EXTERNAL-IP:80进行 curl,看看它是否正常工作:
# 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
  1. 如果我们在 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
  1. 目标池是一组实例,它们接收来自转发规则的流量。我们也可以使用 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.yamlechoserver.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 映射都已创建。我们可以通过访问 GCP 控制台中的 GKE 中的发现和负载均衡或网络服务中的负载均衡选项卡来全面了解情况:

发现和负载均衡

后端服务如下所示:

后端服务

您的 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

我们已经准备就绪。Nginxechoserver可以通过外部静态 IP130.211.37.61访问,并且我们可以通过在 GCP 中使用云 DNS 服务来将 DNS 名称与其关联。

还有更多...

在 Kubernetes v.1.9 中,Kubernetes 云控制器管理器被提升为 alpha 版。云控制器管理器旨在通过其自身的发布周期支持云提供商的发布功能,这可以独立于 Kubernetes 的发布周期。然后它可以独立于 Kubernetes 核心发布周期。它提供了每个云提供商都可以实现的通用接口,与 Kubernetes 核心逻辑解耦。在不久的将来,我们将看到来自不同云提供商的更全面的支持!

另请参阅

  • 在第二章中使用服务深入了解 Kubernetes 概念

  • 在第二章中使用卷深入了解 Kubernetes 概念

  • 在第三章中转发容器端口玩转容器

在 GKE 上管理 Kubernetes 集群

Google Kubernetes Engines 为我们提供了运行 Kubernetes 的无缝体验;它还使 Kubernetes 管理变得如此简单。根据预期的高峰时间,我们可能希望扩展或缩小 Kubernetes 节点。或者,我们可以使用自动缩放器来对节点进行自动缩放。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

调整大小命令可以帮助您扩展和缩小。如果调整大小后的节点数少于之前,调度器将迁移 pod 以在可用节点上运行。

您可以为规范中的每个容器设置计算资源边界。您可以为 pod 容器设置请求和限制。假设我们有一个需要 1024 MB 内存的超级 nginx:

# 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-miro,每个节点只有 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 

区域集群

区域集群仍处于测试阶段。要使用这些,我们需要启用 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

该命令与创建集群的命令非常相似,只有两个不同之处:在组名 container 之前添加了一个 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)。一个在本地主机上,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 还支持客户端证书和令牌身份验证。有关更多信息,请使用标志-h执行set-credentials命令,以详细介绍其功能:

// 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.

此时,请检查您的 live 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-clusterdelete-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 的设置,Kubernetes 将每个 Pod 分类为以下之一:

  • 保证的 Pod

  • 可突发的 Pod

  • 最佳努力的 Pod

优先级为保证 > 可突发 > 最佳努力。例如,如果在同一 Kubernetes 节点中存在一个最佳努力的 Pod 和一个保证的 Pod,并且该节点遇到 CPU 问题或内存耗尽,Kubernetes 主节点将首先终止最佳努力的 Pod。让我们看看它是如何工作的。

准备就绪

有两种设置资源 QoS 的方法:pod 配置或命名空间配置。如果将资源 QoS 设置为命名空间,它将应用于属于同一命名空间的所有 pod。如果将资源 QoS 设置为 pod,它将仅应用于该 pod。此外,如果同时将其设置为命名空间和 pod,它将首先从命名空间配置中获取一个值,然后用 pod 配置覆盖它。因此,我们将设置两个命名空间,一个具有资源 QoS,另一个没有资源 QoS,以查看它们之间的不同:

  1. 使用kubectl命令创建两个命名空间:
$ kubectl create namespace chap8-no-qos
namespace "chap8-no-qos" created

$ kubectl create namespace chap8-qos
namespace "chap8-qos" created
  1. 准备一个 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
  1. 通过输入kubectl命令,使其仅适用于chap8-qos命名空间:
$ kubectl create -f resource-request-cpu.yml --namespace=chap8-qos
limitrange "resource-request-cpu" created
  1. 使用kubectl命令检查chap8-qoschap8-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 类中具有最低的优先级。因此,在资源短缺的情况下,Kubernetes 调度程序将终止此 BestEffort pod,然后将 CPU 和内存资源让给其他优先级更高的 pod。

为了将 pod 配置为 BestEffort,您需要将资源限制设置为0(显式),或者不指定资源限制(隐式)。

  1. 准备一个 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
  1. chap8-qoschap8-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
  1. 检查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-qoschap8-no-qos之间的结果将会不同。以下示例演示了命名空间设置如何影响结果:

  1. chap8-qoschap8-no-qos命名空间中删除之前的 pod:
$ kubectl delete pod --all --namespace=chap8-qos
pod "besteffort" deleted

$ kubectl delete pod --all --namespace=chap8-no-qos
pod "besteffort" deleted
  1. 准备一个没有资源设置的 pod 配置:
$ cat besteffort-implicit.yml
apiVersion: v1
kind: Pod
metadata:
  name: besteffort
spec:
  containers:
  - name: nginx
    image: nginx
  1. 在两个命名空间上创建 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
  1. 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

保证类具有资源QoS类的最高优先级。在资源短缺的情况下,Kubernetes 调度程序将尽量保留保证的 pod。

为了将 pod 配置为具有保证类,明确设置资源限制和资源请求为相同的值,或者只设置资源限制:

  1. 准备一个 pod 配置,resources.limitresources.request具有相同的值:
$ 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
  1. chap8-no-qos命名空间上创建 pod:
$ kubectl create -f guaranteed.yml --namespace=chap8-no-qos
pod "guaranteed-pod" created
  1. 检查QoS类;它有保证类:
$ kubectl describe pods guaranteed-pod --namespace=chap8-no-qos |grep QoS
QoS Class:       Guaranteed

配置一个可突发的 pod

可突发的 pod 的优先级高于 BestEffort,但低于 Guaranteed。为了将 pod 配置为可突发 Pod,您需要设置resources.requestresources.limit是可选的,但resources.requestresources.limit的值不能相等:

  1. 准备一个只有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
  1. 创建 pod:
$ kubectl create -f burstable.yml --namespace=chap8-no-qos
pod "burstable-pod" created
  1. 检查QoS类;它是可突发
$ kubectl describe pods burstable-pod --namespace=chap8-no-qos |grep QoS
QoS Class:       Burstable

工作原理...

让我们看看资源请求/限制如何影响资源管理。先前的可突发的 YAML 配置通过不同的阈值声明了请求和限制:

资源定义类型 资源名称 描述
请求 CPU 0.1 至少占用 1 个 CPU 核心的 10%
内存 10Mi 至少 10 兆字节的内存
限制 CPU 0.5 最大 1 个 CPU 核心的 50%
内存 300Mi 最大 300 兆字节的内存

对于 CPU 资源,可接受的值表达式要么是核心(0.1、0.2...1.0、2.0),要么是毫核(100 m、200 m...1000 m、2000 m)。1000 m 相当于 1.0 个核心。例如,如果 Kubernetes 节点有 2 个核心 CPU(或者 1 个带超线程的核心),则总共有 2.0 个核心或 2000 毫核,如下图所示:

代表 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 和大约 1945 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....

复制令牌并粘贴到浏览器控制台中,然后点击“登录”:

使用服务帐户的令牌进行身份验证

欢迎来到仪表板主页:

Kubernetes 仪表板的主页

它是如何工作的...

Kubernetes 仪表板有两个主要功能:检查资源的状态和部署资源。它可以覆盖我们在客户端终端使用kubectl命令的大部分工作,但是图形界面更加友好。

通过仪表板浏览您的资源

我们可以在仪表板上检查硬件和软件资源。例如,要查看集群中的节点,请在左侧菜单的“集群”部分下点击“节点”;当前集群中的每个节点将显示在页面上,并附有一些基本信息:

仪表板上 Kubernetes 节点的状态

您屏幕上的结果可能与上述截图不同,因为它将基于您的环境。继续点击一个节点的名称;甚至会显示更多详细信息。其中一些以美丽的图表呈现:

计算节点资源状态

展示软件资源,让我们来看看持有这个仪表板的资源。在左侧菜单中,将 Namespace 更改为 kube-system,并单击概述,这将汇总该 Namespace 下的所有资源。通过在单个页面上将资源放在一起并使用清晰的图表,很容易找出任何问题:

kube-system 命名空间的资源概述

还有更多;单击 kubernetes-dashboard 的部署,然后单击副本集中唯一 pod 右侧的小文本文件图标。您可以查看容器的日志:

kubernetes-dashboard 的部署信息仪表板应用程序的日志

现在,我们已经看到 Kubernetes 仪表板提供了一个出色的界面,用于显示资源状态,包括节点、Kubernetes 工作负载和控制器,以及应用程序日志。

通过仪表板部署资源

在这里,我们将准备一个 YAML 配置文件,用于在新的 Namespace 下创建 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 按钮:

通过配置文件创建资源

不幸的是,发生了错误:

由于错误部署而导致的问题的错误消息

仪表板根据左侧菜单上用户选择的 Namespace 显示资源。此错误消息弹出并告诉用户,文件中提到的 Namespace 与仪表板中的不匹配。我们需要做的是创建一个新的 Namespace 并切换到它。

这一次,我们将使用纯文本创建一个 Namespace。再次单击 CREATE 按钮,并选择从文本输入方法创建。将以下行粘贴到网页上以创建一个新的 Namespace:

apiVersion: v1
kind: Namespace
metadata:
  name: dashboard-test

现在,我们有一个新的 Namespace,dashboard-test。在仪表板上选择它作为主 Namespace,并再次提交my-nginx.yaml文件:

在提交配置文件之前选择正确的 Namespace

现在您可以查看此部署的概述!黄色圆圈表示挂起状态。一旦 pod 准备就绪,它们将变为绿色,或者如果失败,它们将变为红色,但是如果您按照以下步骤操作,您将看不到红色的圆圈:

创建资源的状态图

通过仪表板删除资源

我们还可以通过仪表板删除 Kubernetes 资源。尝试自己找到我们刚刚创建的 Service my-nginx!执行以下操作:

  • 在左侧菜单上更改 Namespace 为 dashboard-test

  • 单击左侧菜单中的 Discovery and load balancing 部分下的 Services

  • 单击超链接名称上的 Service my-nginx

  • 单击页面右上角的 DELETE,位于 CREATE 按钮下方

就是这样!一旦您看到屏幕上弹出确认消息,只需单击即可。最后,您不仅创建了一个资源,还从 Kubernetes 仪表板中删除了它。

另请参阅

本教程介绍了如何启动一个 Web 界面,以便轻松地探索和管理 Kubernetes 实例,如 pod、部署和服务,而无需使用kubectl命令。请参考第二章中的以下教程,了解如何通过kubectl命令获取详细信息。

  • 在第二章中的使用 Pod部署 API使用服务教程,深入了解 Kubernetes 概念

使用 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 的 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 中访问资源时,您可能会看到显示“权限被拒绝”的消息。如果是这样,解决方案是将默认服务账户绑定到管理员角色,即 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 请求,您应该知道要处理哪种资源,以及要在其上执行什么操作。执行以下步骤在参考网页上找到相应的信息:

  1. 选择一个资源。

  2. 选择一个操作,例如读取或写入。

  3. 选择操作的详细信息,例如创建或删除。

  4. 信息将显示在网页的中间面板上。一个可选的步骤是在控制台右上角将kubectl切换到curl。更多细节,比如命令标志,将显示在右侧面板上。

要检查创建部署的信息,您的 Web 控制台可能看起来像这个屏幕截图一样:

使用 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

接下来,尝试删除这个新的 Deployment,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 使用swaggerswagger.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-domain1chap8-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为 pod 和 Kubernetes 服务分配完全 限定域名FQDN)。让我们看看一些不同之处。

pod 的 DNS

Kubernetes 为 pod 分配的域名为<IP 地址>.<命名空间名称>.pod.cluster.local。因为它使用了 pod 的 IP 地址,所以 FQDN 不能保证永久存在,但如果应用程序需要 FQDN,那么拥有它是很好的。

让我们在chap8-domain1chap8-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以捕获这些 pod 的 IP 地址:

$ kubectl get pods -o wide --namespace=chap8-domain**1** 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-domain**2** 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 地址,如下面的步骤所示:

  1. 使用-it选项运行busybox
$ kubectl run -it busybox --restart=Never --image=busybox
  1. 在 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
  1. 还要输入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
  1. 退出 busybox pod,然后删除它以释放资源:
# exit $ kubectl delete pod busybox pod "busybox" deleted

Kubernetes 服务的 DNS

首先,从服务发现的角度来看,Kubernetes 服务的 DNS 是最重要的。这是因为应用程序通常连接到 Kubernetes 服务,而不是连接到 pod。这就是为什么应用程序更经常查找 Kubernetes 服务的 DNS 条目,而不是查找 pod 的原因。

其次,Kubernetes 服务的 DNS 条目将使用 Kubernetes 服务的名称而不是 IP 地址。例如,它看起来像这样:<服务名称>.<命名空间名称>.svc.cluster.local

最后,Kubernetes 服务对 DNS 有两种不同的行为;普通服务或无头服务。普通服务有自己的 IP 地址,而无头服务使用 pod 的 IP 地址。让我们先了解普通服务。

普通服务是默认的 Kubernetes 服务。它将分配一个 IP 地址。执行以下步骤来创建一个普通服务并检查 DNS 的工作原理:

  1. chap8-domain1chap8-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
  1. 通过运行以下命令检查这两个服务的 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
  1. 为了进行名称解析,在前台使用 busybox pod:
$ kubectl run -it busybox --restart=Never --image=busybox 
  1. 在 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
  1. 访问 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>
  1. 退出busybox pod 并删除它:
# exit  $ kubectl delete pod busybox pod "busybox" deleted

DNS 对于普通服务的行为类似于代理;流量会先到达普通服务,然后再分发到 pod。那么无头服务呢?这将在它是如何工作的...部分进行讨论。

StatefulSet 的 DNS

StatefulSet 在第三章中有描述,与容器一起玩耍。它为 pod 名称分配一个序列号,例如,my-nginx-0my-nginx-1my-nginx-2。StatefulSet 还使用这些 pod 名称来分配 DNS 条目,而不是 IP 地址。因为它使用 Kubernetes 服务,FQDN 看起来如下:<StatefulSet 名称>-<序列号>.<服务名称>.<命名空间名称>.svc.cluster.local

让我们创建 StatefulSet 来检查 StatefulSet 中 DNS 是如何工作的:

  1. 准备 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
  1. chap8-domain2上创建 StatefulSet:
$ kubectl create -f nginx-sts.yaml --namespace=chap8-domain2 service "nginx-sts-svc" created
statefulset "nginx-sts" created
  1. 使用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
  1. 在前台启动busybox pod:
$ kubectl run -it busybox --restart=Never --image=busybox 
  1. 使用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
  1. 使用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
  1. 清理busybox pod:
# exit $ kubectl delete pod busybox pod "busybox" deleted

它是如何工作的...

我们已经设置了几个组件来查看最初如何创建 DNS 条目。Kubernetes 服务名称对于确定 DNS 的名称尤为重要。

然而,Kubernetes 服务有两种模式,即普通服务或无头服务。普通服务已在前一节中描述过;它有自己的 IP 地址。另一方面,无头服务没有 IP 地址。

让我们看看如何创建一个无头服务以及名称解析是如何工作的:

  1. chap8-domain1chap8-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
  1. 使用以下命令检查这两个无头服务是否没有 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
  1. 在前台启动busybox pod:
$ kubectl run -it busybox --restart=Never --image=busybox 
  1. busybox pod 中,查询这两个服务。它必须显示地址作为 pod 的地址(172.168.0.4172.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 
  1. 退出busybox pod 并删除它:
# 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 中为 pod 和服务命名。了解普通服务和无头服务之间的区别对于理解如何连接到您的应用程序非常重要。下一节还描述了 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>

我们可能会发现与此服务帐户关联的一个 Secret。这由令牌控制器管理。当创建新的服务帐户时,控制器将创建一个令牌,并使用kubernetes.io/service-account.name注释将其与服务帐户关联,从而允许 API 访问。在 Kubernetes 中,令牌以 Secret 格式存在。拥有 Secret 查看权限的任何人都可以看到令牌。以下是创建服务帐户的示例:

// 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>

认证

Kuberentes 支持几种帐户认证策略,从客户端证书、持有者令牌和静态文件到 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 服务器期望在请求中使用Authorization: Bearer $TOKEN的 HTTP 头。以下是如何使用令牌进行身份验证并直接向 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 客户端证书

用户帐户的常见身份验证策略是使用客户端证书。在下面的示例中,我们将创建一个名为琳达的用户,并为她生成一个客户端证书:

// 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

我们已经用我们集群证书签署了琳达;现在我们可以将其设置到我们的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 应该能够识别琳达并将其传递到授权阶段。

OpenID 连接令牌

另一种流行的身份验证策略是 OpenID 连接令牌。将身份验证委托给 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 请求和下载密钥的步骤:

  1. 在 GCP 控制台中,转到 API 和服务|凭据|创建凭据|OAuth 客户端 ID。

  2. 在应用程序类型中选择其他,然后单击创建。

  3. 下载 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_tokenid_tokenrefresh_token。在 Google 中,登录后您将获得一个代码,然后将代码与请求一起传递以获取令牌。然后,我们通过 kubectl 将令牌传递给 API 服务器的请求。以下是此过程的顺序图:

Google OpenID 连接身份验证的时间图

要请求代码,您的应用程序应以以下格式发送 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_tokenrefresh_tokenid_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 

我们遇到了授权错误!在验证身份后,下一步将是检查用户是否有足够的权限来执行请求。

授权

经过身份验证阶段后,授权者开始工作。在我们继续讨论授权策略之前,让我们先谈谈RoleRoleBinding

Role 和 RoleBinding

Kubernetes 中的Role包含一组规则。规则通过指定apiGroupsresourcesverbs来定义某些操作和资源的权限集。例如,以下角色定义了对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

RoleRoleBinding是有命名空间的。它们的范围仅限于单个命名空间。要访问整个集群资源,我们需要ClusterRoleClusterRoleBinding

要将命名空间添加到RoleRoleBinding中,只需在配置文件的元数据中添加一个命名空间字段。

ClusterRole 和 ClusterRoleBinding

ClusterRoleClusterRoleBinding基本上类似于RoleRoleBinding。与RoleRoleBinding仅限于单个命名空间的方式不同,ClusterRoleClusterRoleBinding用于授予整个集群范围的资源。因此,可以将对所有命名空间、非命名空间资源和非资源端点的访问授予ClusterRole,并且我们可以使用ClusterRoleBinding将用户和角色绑定。

我们还可以将服务账户与ClusterRole绑定。由于服务账户是有命名空间的,我们必须指定其完整名称,其中包括它所在的命名空间:

system:serviceaccount:<namespace>:<serviceaccountname>

以下是ClusterRoleClusterRoleBinding的示例。在此角色中,我们授予了许多资源的所有操作权限,例如deploymentsreplicasetsingressespodsservices,并且我们将命名空间和事件的权限限制为只读:

# 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-serviceaccountcd-role关联起来。

基于角色的访问控制(RBAC)

基于角色的访问控制的概念围绕着RoleClusterRoleRoleBindingClusterRoleBinding。通过role.yamlrolebinding.yaml,正如我们之前展示的,Linda 应该对configmaps资源获得只读访问权限。要将授权规则应用于chloe-k8scookbook@gmail.com,只需将ClusterRoleClusteRoleBinding与其关联即可:

# 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(版本<1.10)--enable-admission-plugins(版本>=1.10)参数。

根据集群的配置方式,传递--enable-admission-plugin参数的方法可能会有所不同。在 minikube 中,添加--extra-config=apiserver.Admission.PluginNames=$ADMISSION_CONTROLLERS并用逗号分隔不同的控制器应该就可以了。

不同的准入控制器设计用于不同的目的。在接下来的教程中,我们将介绍一些重要的准入控制器以及 Kubernetes 官方建议用户拥有的准入控制器。版本>=1.6.0 的推荐列表如下:NamespaceLifecycleLimitRangerServiceAccountPersistentVolumeLabelDefaultStorageClassDefaultTolerationSecondsResourceQuota

请注意,准入控制器的顺序很重要,因为请求会依次通过(这对于 1.10 版本之前使用--admission-control选项的情况是正确的;在 v1.10 中,该参数被--enable-admission-plugins替换,顺序就不再重要)。我们不希望首先进行ResourceQuota检查,然后在检查了一长串准入控制器后发现资源信息已过时。

如果版本是>=1.9.0,则MutatingAdmissionWebhookValidatingAdmissionWebhook将在ResourceQuota之前添加。有关MutatingAdmissionWebhookValidatingAdmissionWebhook的更多信息,请参阅本教程中的更多内容部分。

NamespaceLifecycle

当命名空间被删除时,该命名空间中的所有对象也将被清除。此插件确保在终止或不存在的命名空间中无法进行新对象的创建请求。它还可以防止 Kubernetes 本机命名空间被删除。

LimitRanger

此插件确保 LimitRange 可以正常工作。使用 LimitRange,我们可以在命名空间中设置默认请求和限制,在启动 pod 时使用,而无需指定请求和限制。

ServiceAccount

如果您打算在用例中利用 ServiceAccount 对象,则必须添加 ServiceAccount 插件。有关 ServiceAccount 的更多信息,请重新查看本教程中学到的 ServiceAccount 部分。

PersistentVolumeLabel(从 v1.8 版本开始已弃用)

PersistentVolumeLabel 根据底层云提供商提供的标签,为新创建的 PV 添加标签。从 1.8 版本开始,此准入控制器已被弃用。此控制器的功能现在由云控制器管理器负责,它定义了特定于云的控制逻辑并作为守护程序运行。

默认存储类

此插件确保默认存储类在未在 PersistentVolumeClaim 中设置 StorageClass 的情况下可以正常工作。不同的云提供商使用不同的供应工具来利用 DefaultStorageClass(例如 GKE 使用 Google Cloud Persistent Disk)。请确保您已启用此功能。

默认容忍时间

污点和容忍度用于阻止一组 pod 在某些节点上调度运行。污点应用于节点,而容忍度则针对 pod 进行指定。污点的值可以是 NoScheduleNoExecute。如果在一个带有污点的节点上运行的 pod 没有匹配的容忍度,那么这些 pod 将被驱逐。

DefaultTolerationSeconds 插件用于设置那些没有设置容忍度的 pod。然后,它将为 notready:NoExecuteunreachable:NoExecute 的默认容忍度申请 300 秒。如果节点不可用或不可达,等待 300 秒后再将 pod 从节点中驱逐。

ResourceQuota

就像 LimitRange 一样,如果您正在使用 ResourceQuota 对象来管理不同级别的 QoS,则必须启用此插件。ResourceQuota 应始终放在准入控制插件列表的末尾。正如我们在 ResourceQuota 部分提到的,如果使用的配额少于硬配额,资源配额使用将被更新,以确保集群有足够的资源来接受请求。将其放在 ServiceAccount 准入控制器列表的末尾可以防止请求在被后续控制器拒绝之前过早增加配额使用。

DenyEscalatingExec

这个插件拒绝了任何 kubectl exec 和 kubectl attach 命令的提升特权模式。具有特权模式的 pod 可以访问主机命名空间,这可能会带来安全风险。

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,因此默认情况下已启用。

Initializers(alpha)

Initializers 是对象初始化阶段的一组任务。它们可以是一组检查或变更,用于执行强制策略或注入默认值。例如,你可以实现一个 Initializer 来向 pod 注入一个 sidecar 容器或包含测试数据的卷。Initializers 在对象的 metadata.initializers.pending 中进行配置。在相应的 Initializer 控制器(通过名称标识)执行任务后,它将从元数据中删除其名称。如果由于某些原因某个 Initializer 不起作用,所有具有该 Initializer 的对象将被卡在未初始化阶段,并且在 API 中不可见。请谨慎使用。

Webhook 准入控制器(v1.9 中的 beta 版本)

截至 v1.10,有两种类型的 webhook 准入控制器:

  • ValidatingAdmissionWebhook:它可以进行额外的自定义验证来拒绝请求

  • MutatingAdmissionWebhooks:它可以改变对象以强制执行默认策略

有关更多实施信息,请参考官方文档:

kubernetes.io/docs/admin/extensible-admission-controllers/

参见

以下食谱与本节相关:

  • 在第二章中的使用命名空间*

  • 在第五章中的设置持续交付流水线,构建持续交付流水线*

  • 在第八章中的kubeconfig 的高级设置,高级集群管理*

  • 在第八章中的使用 ServiceAccount RESTful API,高级集群管理*

第九章:日志记录和监控

本章将涵盖以下内容:

  • 使用 EFK

  • 使用 Google Stackdriver 进行工作

  • 监控主节点和节点

介绍

日志记录和监控是 Kubernetes 中最重要的任务之一。然而,在 Kubernetes 中实现日志记录和监控有许多方法,因为有许多日志记录和监控开源应用程序,以及许多公共云服务。

Kubernetes 有一个最佳实践,用于设置大多数 Kubernetes 配置工具支持的日志记录和监控基础设施的附加组件。此外,托管的 Kubernetes 服务,如 Google Kubernetes Engine,集成了 GCP 日志和监控服务。

让我们在您的 Kubernetes 集群上设置日志记录和监控服务。

使用 EFK

在容器世界中,日志管理总是面临技术困难,因为容器有自己的文件系统,当容器死亡或被驱逐时,日志文件就会消失。此外,Kubernetes 可以轻松地扩展和缩减 Pods,因此我们需要关注一个集中式的日志持久化机制。

Kubernetes 有一个用于设置集中式日志管理的附加组件,称为 EFK。EFK 代表ElasticsearchFluentdKibana。这些应用程序的堆栈为您提供了完整的日志收集、索引和 UI 功能。

准备工作

在第一章中,构建您自己的 Kubernetes 集群,我们使用了几种不同的配置工具来设置我们的 Kubernetes 集群。根据您的 Kubernetes 配置工具,有一种简单的方法可以设置 EFK 堆栈。请注意,Elasticsearch 和 Kibana 是重型的 Java 应用程序。它们每个都需要至少 2GB 的内存。

因此,如果您使用 minikube,您的计算机应至少有 8GB 的内存(建议使用 16GB)。如果您使用 kubespray 或 kops 来设置 Kubernetes 集群,Kubernetes 节点应至少具有总共 4 个核心 CPU 和 16GB 的内存(换句话说,如果您有 2 个节点,每个节点应至少具有 2 个核心 CPU 和 8GB 的内存)。

此外,为了演示如何有效地收集应用程序日志,我们创建了一个额外的命名空间。这将帮助您轻松搜索您的应用程序日志:

$ 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 的内存大小。

此外,您应该使用最新版本的 minikube,因为在编写本教程时对 EFK 进行了几个错误修复。因此,我们使用 minikube 版本 0.25.2。让我们按照以下步骤配置 minikube 以启用 EFK:

  1. 如果您已经运行minikube,请先停止minikube
$ minikube stop
  1. 更新到最新版本的 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 
  1. 由于 EFK 消耗大量堆内存,使用 5GB 内存启动minikube
$ minikube start --vm-driver=hyperkit --memory 5120
  1. 确保kube-system命名空间中的所有 Pod 都已启动,因为 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
  1. 启用efk插件:
$ minikube addons enable efk
efk was successfully enabled
  1. 等待一段时间;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
…
  1. 使用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"}
  1. 使用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-*。点击Create按钮:

使用 kubespray 设置 EFK

kubespray 有一个关于是否启用 EFK 的配置。默认情况下是禁用的,因此您需要按照以下步骤启用它:

  1. 打开<kubespray dir>/inventory/mycluster/group_vars/k8s-cluster.yaml

  2. k8s-cluster.yml文件的第 152 行左右,将efk_enabled的值更改为true

# Monitoring apps for k8s
efk_enabled: true
  1. 运行ansible-playbook命令来更新您的 Kubernetes 集群:
$ ansible-playbook -b -i inventory/mycluster/hosts.ini cluster.yml
  1. 检查 Elasticsearch、Fluentd 和 Kibana Pod 的状态是否变为 Running;如果看到超过 10 分钟的 Pending 状态,请检查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
…
  1. 检查 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"}
  1. 运行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
  1. 为了从远程机器访问 Kibana WebUI,最好使用 ssh 端口转发从您的机器到 Kubernetes 主节点:
$ ssh -L 8080:127.0.0.1:8080 <Kubernetes master IP address>
  1. 使用以下 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 堆栈:

  1. 运行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
  1. 等待所有 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
...
  1. 检查 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"}
  1. 运行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
  1. 使用kubectl proxy将您的机器转发到 Kubernetes API 服务器:
$ kubectl proxy --port=8080
Starting to serve on 127.0.0.1:8080
  1. 使用以下 URL 从您的机器访问 Kibana WebUI:http://127.0.0.1:8080/api/v1/namespaces/kube-system/services/kibana-logging/proxy。请注意 IP 地址为 127.0.0.1,这是正确的,因为我们正在使用 kubectl 代理。

现在,您可以开始使用 Kibana。按照前面的 minikube 和 kubespray 部分的描述配置索引。

工作原理...

如您所见,根据 Kubernetes 配置工具,安装的 Kibana 版本是不同的。但是,本教程探讨了 Kibana 的基本功能。因此,无需担心特定于版本的操作。

让我们启动一个示例应用程序,然后学习如何使用 Kibana 监视应用程序日志:

  1. 准备一个示例应用程序,该应用程序不断打印DateTime和 hello 消息到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
  1. chap9命名空间中创建一个示例应用程序:
$ kubectl create -f myapp.yaml --namespace=chap9
pod "myapp" created
  1. 访问 Kibana WebUI,然后点击 Discover 选项卡:

  2. 确保时间范围为“最近 15 分钟”,然后在搜索框中输入kubernetes.namespace_name: chap9,然后按下Enter键:

在 15 分钟内搜索 chap9 命名空间的日志

  1. 您可以看到chap9命名空间中的所有日志如下。屏幕截图显示的信息比您预期的要多得多。通过点击kubernetes.hostkubernetes.pod_namelog的添加按钮,将仅显示此目的所需的字段:

选择日志列

  1. 现在您可以看到这个应用程序的更简单的日志视图:

显示自定义 Kibana 视图的最终状态

恭喜!您现在在您的 Kubernetes 集群中拥有了一个集中式日志管理系统。您可以观察一些 Pod 的部署,看看如何查看应用程序日志。

还有更多...

前面的 EFK 堆栈只收集 Pod 的日志,因为 Fluentd 正在监视 Kubernetes 节点主机上的/var/log/containers/*。这已经足够监视应用程序的行为,但作为 Kubernetes 管理员,您还需要一些 Kubernetes 系统日志,如主节点和节点日志。

有一个简单的方法可以实现与 EFK 堆栈集成的 Kubernetes 系统日志管理;添加一个 Kubernetes 事件导出器,它会持续监视 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 的详细信息将在下一节“监控主节点和节点”中描述。

  1. 将 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
  1. 确保 Eventer Pod 的STATUSRunning
$ 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
...
  1. 使用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**
  1. 出于测试目的,打开另一个终端,然后创建一个nginx Pod:
$ kubectl run my-nginx --image=nginx deployment "my-nginx" created
  1. 观察 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
  1. 打开 Kibana 并导航到设置|索引|添加新内容。这将添加一个新索引。

  2. 将索引名称设置为heapster-*,将时间字段名称设置为Metadata.creationTimestamp,然后单击“创建”:

配置 Heapster 索引

  1. 返回“发现”页面,然后从左侧面板中选择heapster-*索引。

  2. 选择(单击“添加”按钮)“Message”,“Source.component”和“Source.host”:

选择必要的列

  1. 现在您可以看到 Kubernetes 系统日志,其中显示了nginx Pod 的创建事件如下:

显示 Kibana 中系统日志视图的最终状态

现在您不仅可以监视应用程序日志,还可以在 EFK 堆栈中监视 Kubernetes 系统日志。通过在logstash-*(应用程序日志)或heapster-*(系统日志)之间切换索引,您可以获得灵活的日志管理环境。

另请参阅

在本食谱中,我们学习了如何为您的 Kubernetes 集群启用 EFK 堆栈。Kibana 是一个强大的工具,您可以使用它创建自己的仪表板并更有效地检查日志。请访问 Kibana 的在线文档以了解如何使用它:

使用 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

如何做…

为了演示 GKE 的 Stackdriver,我们将在 Kubernetes 上创建一个命名空间,然后启动一个示例 Pod 以查看 Stackdriver 上的一些日志,如下所示:

  1. 创建chap9命名空间:
$ kubectl create namespace chap9
namespace "chap9" created
  1. 准备一个示例应用程序 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
  1. chap9命名空间上创建 Pod:
$ kubectl create -f myapp.yaml --namespace=chap9 pod "myapp" created
  1. 访问 GCP Web 控制台,导航到 Logging | Logs。

  2. 选择 Audited Resources | GKE Container | Your GKE cluster name (ex: my-gke) | chap9 namespace:

选择 chap9 命名空间 Pod 日志

  1. 作为访问chap9命名空间日志的另一种方式,您可以选择高级过滤器。 然后,将以下标准输入到文本字段中,然后单击 Submit Filter 按钮:

使用高级过滤器

resource.type="container" 
resource.labels.cluster_name="<Your GKE name>"
resource.labels.namespace_id="chap9"

输入高级过滤器标准

  1. 现在,您可以在 Stackdriver 上看到myapp日志:

在 Stackdriver 中显示 chap9 Pod 日志

它是如何工作的...

Stackdriver 具有缩小日期、严重性和关键字搜索的基本功能。它有助于监视应用程序的行为。那么系统级行为呢,比如主节点活动?Stackdriver 还支持系统级日志的搜索。实际上,fluentd不仅捕获应用程序日志,还捕获系统日志。通过执行以下步骤,您可以在 Stackdriver 中查看系统日志:

  1. 选择 GKE Cluster Operations | Your GKE name (例如,my-gke) | All location:

您应该选择 All location 而不是特定位置,因为某些 Kubernetes 操作日志不包含位置值。 在 Stackdriver 中选择 GKE 系统日志

  1. 作为替代,输入以下高级过滤器:
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(https://github.com/kubernetes/heapster)的,它是一个资源使用收集和分析工具。Heapster 与 kubelet 通信,获取机器和容器的资源使用情况。除了 Heapster,我们还有 influxDB(https://influxdata.com)用于存储,以及 Grafana(http://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。检查节点和 Pod 的利用率,并验证监控功能的功能:

// 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只涵盖节点和 Pod,并显示它们的 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

您可以通过以下 URL 访问 Grafana:http://localhost:8001/api/v1/namespaces/kube-system/services/monitoring-grafana/proxy/。使我们能够看到网页的魔法是由 Kubernetes DNS 服务器和代理完成的:

在反 minikube Kubernetes 中访问 Grafana 仪表板

要通过浏览器访问 Grafana,这取决于节点的网络配置和 Grafana 的 Kubernetes 服务。按照以下步骤将网页转发到您的客户端:

  • 升级 Grafana 的服务类型:我们应用的配置文件创建了一个带有 ClusterIP 服务的 Grafana。您应该将其更改为NodePortLoadBalancer,以将 Grafana 暴露给外部世界。

  • 检查防火墙:确保您的客户端或负载均衡器能够访问集群的节点。

  • 通过目标端口访问仪表板:与我们在 minikube 集群上所做的详细 URL 不同,您可以使用简单的 URL 访问 Grafana,例如NODE_ENTRYPOINT:3000(Grafana 默认在配置文件中请求端口 3000)或负载均衡器的入口点。

Grafana 的主页

在 Grafana 的默认设置中,我们有两个仪表板,ClusterPodsCluster仪表板涵盖了节点的资源利用情况,如 CPU、内存、网络事务和存储。Pods仪表板为每个 Pod 提供了类似的图表,您可以检查 Pod 中的每个容器:

通过 Pod 仪表板查看 Pod kube-dns

如前面的截图所示,例如,我们可以观察kube-system命名空间中kube-dns Pod 中各个容器的 CPU 利用率,这是 DNS 服务器的集群。您可以发现这个 Pod 中有三个容器,kubednsdnsmasqsidecar,图中的线分别表示容器的 CPU 限制、请求和实际使用情况。

创建一个新的指标来监控 Pod

对于正在运行的应用程序,指标是我们可以收集和用来分析其行为和性能的数据。指标可以来自系统方面,比如 CPU 的使用,也可以基于应用程序的功能,比如某些功能的请求频率。Heapster 提供了几个用于监控的指标(github.com/kubernetes/heapster/blob/master/docs/storage-schema.md)。我们将向您展示如何自己创建一个定制面板。请参考以下步骤:

  1. 进入 Pod 的仪表板,将网页拖动到底部。有一个名为“添加行”的按钮;点击它以添加一个指标。然后,选择第一个类别图形作为表达这个指标的新面板:

使用图表表达添加新的指标

  1. 出现一个空的面板块。继续点击它进行进一步配置。当您选择面板后,编辑块出现时,只需选择编辑:

开始编辑一个面板

  1. 首先,给你的面板起一个名字。例如,CPU 利用率。我们想要创建一个显示 CPU 利用率的面板:

在“常规”页面上给面板一个标题

  1. 设置以下参数以进行特定数据查询。参考以下截图:
  • FROM: cpu/usage_rate

  • WHERE: type = pod_container

  • AND: namespace_name=$namespace, pod_name=$podname value

  • GROUP BY: tag(container_name)

  • ALIAS BY: $tag_container_name

CPU 利用率指标的数据查询参数

  1. 是否有任何状态线显示?如果没有,修改显示页面中的配置将帮助您构建最好看的图表。使空值连接,您将发现线条显示出来:

编辑您的指标外观。检查空值是否为“连接”以显示线条

  1. 就是这样!随意关闭编辑模式。现在您为每个 Pod 都有了一个新的指标。

尝试发现 Grafana 仪表板和 Heapster 监控工具的更多功能。通过监控系统的信息,您将获得有关系统、服务和容器的进一步细节。

还有更多...

我们建立了一个基于 Heapster 的监控系统,由 Kubernetes 小组维护。然而,一些专注于容器集群的工具和平台已经涌现出来,以支持社区,比如 Prometheus (prometheus.io)。另一方面,公共云可能会在 VM 上运行守护程序来默认抓取指标,并提供相应的服务。我们不必在集群内构建一个。接下来,我们将介绍在 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 中有多个服务类别。选择监控以检查相关功能。

作为新用户,您将获得 30 天的免费试用。初始配置很简单;只需启用一个帐户并绑定您的项目。由于我们只是想检查 GKE 集群,您可以避免安装代理和设置 AWS 帐户。成功登录 Stackdriver 后,点击左侧面板上的资源,然后选择基础设施类型下的 Kubernetes Engine。

GKE on Strackdriver 监控的主页

已经为从节点到容器的计算资源设置了多个指标。花些时间探索并查看官方介绍以获取更多功能: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 技术的最佳方法。

posted @ 2024-05-20 12:00  绝不原创的飞龙  阅读(21)  评论(0编辑  收藏  举报