CINXE.COM
教程 | Kubernetes
<!doctype html><html itemscope itemtype=http://schema.org/WebPage lang=zh-cn class=no-js><head><meta name=robots content="noindex, nofollow"><link rel=alternate hreflang=en href=https://kubernetes.io/docs/tutorials/><link rel=alternate hreflang=bn href=https://kubernetes.io/bn/docs/tutorials/><link rel=alternate hreflang=fr href=https://kubernetes.io/fr/docs/tutorials/><link rel=alternate hreflang=de href=https://kubernetes.io/de/docs/tutorials/><link rel=alternate hreflang=hi href=https://kubernetes.io/hi/docs/tutorials/><link rel=alternate hreflang=id href=https://kubernetes.io/id/docs/tutorials/><link rel=alternate hreflang=it href=https://kubernetes.io/it/docs/tutorials/><link rel=alternate hreflang=ja href=https://kubernetes.io/ja/docs/tutorials/><link rel=alternate hreflang=ko href=https://kubernetes.io/ko/docs/tutorials/><link rel=alternate hreflang=pl href=https://kubernetes.io/pl/docs/tutorials/><link rel=alternate hreflang=pt-br href=https://kubernetes.io/pt-br/docs/tutorials/><link rel=alternate hreflang=ru href=https://kubernetes.io/ru/docs/tutorials/><link rel=alternate hreflang=es href=https://kubernetes.io/es/docs/tutorials/><link rel=alternate hreflang=uk href=https://kubernetes.io/uk/docs/tutorials/><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name=generator content="Hugo 0.133.0"><link rel=canonical type=text/html href=https://kubernetes.io/zh-cn/docs/tutorials/><link rel="shortcut icon" type=image/png href=/images/kubernetes.png><link rel=icon type=image/png sizes=64x64 href=/icons/favicon-64.png><link rel=icon type=image/png sizes=32x32 href=/icons/favicon-32.png><link rel=icon type=image/png sizes=16x16 href=/icons/favicon-16.png><link rel=apple-touch-icon-256x256 href=/icons/apple-touch-icon-256x256.png><link rel=apple-touch-icon-196x196 href=/icons/apple-touch-icon-196x196.png><link rel=apple-touch-icon-192x192 href=/icons/apple-touch-icon-192x192.png><link rel=apple-touch-icon-180x180 href=/icons/apple-touch-icon-180x180.png><link rel=apple-touch-icon-167x167 href=/icons/apple-touch-icon-167x167.png><link rel=apple-touch-icon-160x160 href=/icons/apple-touch-icon-160x160.png><link rel=apple-touch-icon-152x152 href=/icons/apple-touch-icon-152x152.png><link rel=apple-touch-icon-120x120 href=/icons/apple-touch-icon-120x120.png><link rel=apple-touch-icon-76x76 href=/icons/apple-touch-icon-76x76.png><link rel=icon type=image/png href=/icons/icon-128x128.png sizes=128x128><meta name=theme-color content="#326de6"><title>教程 | Kubernetes</title><meta property="og:url" content="https://kubernetes.io/zh-cn/docs/tutorials/"> <meta property="og:site_name" content="Kubernetes"><meta property="og:title" content="教程"><meta property="og:description" content="Kubernetes 文档的这一部分包含教程。 每个教程展示了如何完成一个比单个任务更大的目标。 通常一个教程有几个部分,每个部分都有一系列步骤。在浏览每个教程之前, 你可能希望将标准化术语表页面添加到书签,供以后参考。 基础知识 Kubernetes 基础知识 是一个深入的交互式教程,帮助你理解 Kubernetes 系统,并尝试一些基本的 Kubernetes 特性。 Kubernetes 介绍 (edX) 你好 Minikube 配置 示例:配置 Java 微服务 使用 ConfigMap 配置 Redis 构造 Pod 采用 Sidecar 容器 无状态应用程序 公开外部 IP 地址访问集群中的应用程序 示例:使用 Redis 部署 PHP 留言板应用程序 有状态应用程序 StatefulSet 基础 示例:WordPress 和 MySQL 使用持久卷 示例:使用有状态集部署 Cassandra 运行 ZooKeeper,CP 分布式系统 服务 使用 Service 连接到应用 使用源 IP 安全 在集群级别应用 Pod 安全标准 在名字空间级别应用 Pod 安全标准 使用 AppArmor 限制容器对资源的访问 Seccomp 集群管理 以独立模式运行 kubelet 接下来 如果你要编写教程,请参阅内容页面类型 以获取有关教程页面类型的信息。"><meta property="og:locale" content="zh_cn"><meta property="og:type" content="website"><meta itemprop=name content="教程"><meta itemprop=description content="Kubernetes 文档的这一部分包含教程。 每个教程展示了如何完成一个比单个任务更大的目标。 通常一个教程有几个部分,每个部分都有一系列步骤。在浏览每个教程之前, 你可能希望将标准化术语表页面添加到书签,供以后参考。 基础知识 Kubernetes 基础知识 是一个深入的交互式教程,帮助你理解 Kubernetes 系统,并尝试一些基本的 Kubernetes 特性。 Kubernetes 介绍 (edX) 你好 Minikube 配置 示例:配置 Java 微服务 使用 ConfigMap 配置 Redis 构造 Pod 采用 Sidecar 容器 无状态应用程序 公开外部 IP 地址访问集群中的应用程序 示例:使用 Redis 部署 PHP 留言板应用程序 有状态应用程序 StatefulSet 基础 示例:WordPress 和 MySQL 使用持久卷 示例:使用有状态集部署 Cassandra 运行 ZooKeeper,CP 分布式系统 服务 使用 Service 连接到应用 使用源 IP 安全 在集群级别应用 Pod 安全标准 在名字空间级别应用 Pod 安全标准 使用 AppArmor 限制容器对资源的访问 Seccomp 集群管理 以独立模式运行 kubelet 接下来 如果你要编写教程,请参阅内容页面类型 以获取有关教程页面类型的信息。"><meta itemprop=dateModified content="2024-10-19T14:47:29+08:00"><meta itemprop=wordCount content="75"><meta name=twitter:card content="summary"><meta name=twitter:title content="教程"><meta name=twitter:description content="Kubernetes 文档的这一部分包含教程。 每个教程展示了如何完成一个比单个任务更大的目标。 通常一个教程有几个部分,每个部分都有一系列步骤。在浏览每个教程之前, 你可能希望将标准化术语表页面添加到书签,供以后参考。 基础知识 Kubernetes 基础知识 是一个深入的交互式教程,帮助你理解 Kubernetes 系统,并尝试一些基本的 Kubernetes 特性。 Kubernetes 介绍 (edX) 你好 Minikube 配置 示例:配置 Java 微服务 使用 ConfigMap 配置 Redis 构造 Pod 采用 Sidecar 容器 无状态应用程序 公开外部 IP 地址访问集群中的应用程序 示例:使用 Redis 部署 PHP 留言板应用程序 有状态应用程序 StatefulSet 基础 示例:WordPress 和 MySQL 使用持久卷 示例:使用有状态集部署 Cassandra 运行 ZooKeeper,CP 分布式系统 服务 使用 Service 连接到应用 使用源 IP 安全 在集群级别应用 Pod 安全标准 在名字空间级别应用 Pod 安全标准 使用 AppArmor 限制容器对资源的访问 Seccomp 集群管理 以独立模式运行 kubelet 接下来 如果你要编写教程,请参阅内容页面类型 以获取有关教程页面类型的信息。"><script async src="https://www.googletagmanager.com/gtag/js?id=G-JPP6RFM2BP"></script><script>var dnt,doNotTrack=!1;if(!1&&(dnt=navigator.doNotTrack||window.doNotTrack||navigator.msDoNotTrack,doNotTrack=dnt=="1"||dnt=="yes"),!doNotTrack){window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}gtag("js",new Date),gtag("config","G-JPP6RFM2BP")}</script><link rel=preload href=/scss/main.min.47937ebb3d7d851e399e3c75540ff973622d74e30e9fc7960b93a9572eface54.css as=style><link href=/scss/main.min.47937ebb3d7d851e399e3c75540ff973622d74e30e9fc7960b93a9572eface54.css rel=stylesheet integrity><script type=application/ld+json>{"@context":"https://schema.org","@type":"Organization","url":"https://kubernetes.io","logo":"https://kubernetes.io/images/favicon.png","potentialAction":{"@type":"SearchAction","target":"https://kubernetes.io/search/?q={search_term_string}","query-input":"required name=search_term_string"}}</script><meta name=theme-color content="#326ce5"><style>.gutter{background-color:#eee;background-repeat:no-repeat;background-position:50%}.gutter.gutter-horizontal{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==);cursor:col-resize}#sidebarnav,#maindoc{max-width:100%}#maindoc{overflow-wrap:break-word}@media(max-width:768px){#sidebarnav{padding-left:15px;padding-right:15px}}</style><meta name=description content="Kubernetes 文档的这一部分包含教程。 每个教程展示了如何完成一个比单个任务更大的目标。 通常一个教程有几个部分,每个部分都有一系列步骤。在浏览每个教程之前, 你可能希望将标准化术语表页面添加到书签,供以后参考。 基础知识 Kubernetes 基础知识 是一个深入的交互式教程,帮助你理解 Kubernetes 系统,并尝试一些基本的 Kubernetes 特性。 Kubernetes 介绍 (edX) 你好 Minikube 配置 示例:配置 Java 微服务 使用 ConfigMap 配置 Redis 构造 Pod 采用 Sidecar 容器 无状态应用程序 公开外部 IP 地址访问集群中的应用程序 示例:使用 Redis 部署 PHP 留言板应用程序 有状态应用程序 StatefulSet 基础 示例:WordPress 和 MySQL 使用持久卷 示例:使用有状态集部署 Cassandra 运行 ZooKeeper,CP 分布式系统 服务 使用 Service 连接到应用 使用源 IP 安全 在集群级别应用 Pod 安全标准 在名字空间级别应用 Pod 安全标准 使用 AppArmor 限制容器对资源的访问 Seccomp 集群管理 以独立模式运行 kubelet 接下来 如果你要编写教程,请参阅内容页面类型 以获取有关教程页面类型的信息。"><meta property="og:description" content="Kubernetes 文档的这一部分包含教程。 每个教程展示了如何完成一个比单个任务更大的目标。 通常一个教程有几个部分,每个部分都有一系列步骤。在浏览每个教程之前, 你可能希望将标准化术语表页面添加到书签,供以后参考。 基础知识 Kubernetes 基础知识 是一个深入的交互式教程,帮助你理解 Kubernetes 系统,并尝试一些基本的 Kubernetes 特性。 Kubernetes 介绍 (edX) 你好 Minikube 配置 示例:配置 Java 微服务 使用 ConfigMap 配置 Redis 构造 Pod 采用 Sidecar 容器 无状态应用程序 公开外部 IP 地址访问集群中的应用程序 示例:使用 Redis 部署 PHP 留言板应用程序 有状态应用程序 StatefulSet 基础 示例:WordPress 和 MySQL 使用持久卷 示例:使用有状态集部署 Cassandra 运行 ZooKeeper,CP 分布式系统 服务 使用 Service 连接到应用 使用源 IP 安全 在集群级别应用 Pod 安全标准 在名字空间级别应用 Pod 安全标准 使用 AppArmor 限制容器对资源的访问 Seccomp 集群管理 以独立模式运行 kubelet 接下来 如果你要编写教程,请参阅内容页面类型 以获取有关教程页面类型的信息。"><meta name=twitter:description content="Kubernetes 文档的这一部分包含教程。 每个教程展示了如何完成一个比单个任务更大的目标。 通常一个教程有几个部分,每个部分都有一系列步骤。在浏览每个教程之前, 你可能希望将标准化术语表页面添加到书签,供以后参考。 基础知识 Kubernetes 基础知识 是一个深入的交互式教程,帮助你理解 Kubernetes 系统,并尝试一些基本的 Kubernetes 特性。 Kubernetes 介绍 (edX) 你好 Minikube 配置 示例:配置 Java 微服务 使用 ConfigMap 配置 Redis 构造 Pod 采用 Sidecar 容器 无状态应用程序 公开外部 IP 地址访问集群中的应用程序 示例:使用 Redis 部署 PHP 留言板应用程序 有状态应用程序 StatefulSet 基础 示例:WordPress 和 MySQL 使用持久卷 示例:使用有状态集部署 Cassandra 运行 ZooKeeper,CP 分布式系统 服务 使用 Service 连接到应用 使用源 IP 安全 在集群级别应用 Pod 安全标准 在名字空间级别应用 Pod 安全标准 使用 AppArmor 限制容器对资源的访问 Seccomp 集群管理 以独立模式运行 kubelet 接下来 如果你要编写教程,请参阅内容页面类型 以获取有关教程页面类型的信息。"><meta property="og:url" content="https://kubernetes.io/zh-cn/docs/tutorials/"><meta property="og:title" content="教程"><meta name=twitter:title content="教程"><meta name=twitter:image content="https://kubernetes.io/images/favicon.png"><meta name=twitter:image:alt content="Kubernetes"><meta property="og:image" content="/images/kubernetes-horizontal-color.png"><meta property="og:type" content="article"><script src=/js/jquery-3.6.0.min.js intregrity=sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GFKzPxzJdPfGK crossorigin=anonymous></script><script src=/js/split-1.6.0.js intregrity=sha384-0blL3GqHy6+9fw0cyY2Aoiwg4onHAtslAs4OkqZY7UQBrR65/K4gI+hxLdWDrjpz></script><link rel=manifest href=/manifest.webmanifest><script defer src=/js/banner-dismiss.min.095ea25f4d8d1299fa348e27bfc7822ce87d0fba4f01d3660670afc7b3184df0.js integrity="sha256-CV6iX02NEpn6NI4nv8eCLOh9D7pPAdNmBnCvx7MYTfA=" crossorigin=anonymous></script></head><body class=td-section><header><nav class="js-navbar-scroll navbar navbar-expand navbar-dark flex-column flex-md-row td-navbar" data-auto-burger=primary><a class="navbar-brand img-fluid" href=/zh-cn/></a><div class="td-navbar-nav-scroll ml-md-auto" id=main_navbar><ul class="navbar-nav mt-2 mt-lg-0"><li class="nav-item mr-2 mb-lg-0"><a class="nav-link active" href=/zh-cn/docs/>文档</a></li><li class="nav-item mr-2 mb-lg-0"><a class=nav-link href=/zh-cn/blog/>Kubernetes 博客</a></li><li class="nav-item mr-2 mb-lg-0"><a class=nav-link href=/zh-cn/training/>培训</a></li><li class="nav-item mr-2 mb-lg-0"><a class=nav-link href=/zh-cn/partners/>合作伙伴</a></li><li class="nav-item mr-2 mb-lg-0"><a class=nav-link href=/zh-cn/community/>社区</a></li><li class="nav-item mr-2 mb-lg-0"><a class=nav-link href=/zh-cn/case-studies/>案例分析</a></li><li class="nav-item mr-n3 mr-lg-0 dropdown"><a class="nav-link dropdown-toggle" href=# id=navbarDropdown role=button data-toggle=dropdown aria-haspopup=true aria-expanded=false>版本列表</a><div class="dropdown-menu dropdown-menu-right" aria-labelledby=navbarDropdownMenuLink><a class=dropdown-item href=/zh-cn/releases>发布信息</a> <a class=dropdown-item href=https://kubernetes.io/zh-cn/docs/tutorials/>v1.31</a> <a class=dropdown-item href=https://v1-30.docs.kubernetes.io/zh-cn/docs/tutorials/>v1.30</a> <a class=dropdown-item href=https://v1-29.docs.kubernetes.io/zh-cn/docs/tutorials/>v1.29</a> <a class=dropdown-item href=https://v1-28.docs.kubernetes.io/zh-cn/docs/tutorials/>v1.28</a> <a class=dropdown-item href=https://v1-27.docs.kubernetes.io/zh-cn/docs/tutorials/>v1.27</a></div></li><li class="nav-item mr-n4 mr-lg-0 dropdown"><a class="nav-link dropdown-toggle" href=# id=navbarDropdownMenuLink role=button data-toggle=dropdown aria-haspopup=true aria-expanded=false>中文 (Chinese)</a><div class="dropdown-menu dropdown-menu-right" aria-labelledby=navbarDropdownMenuLink><a class=dropdown-item href=/docs/tutorials/>English</a> <a class=dropdown-item href=/bn/docs/tutorials/>বাংলা (Bengali)</a> <a class=dropdown-item href=/fr/docs/tutorials/>Français (French)</a> <a class=dropdown-item href=/de/docs/tutorials/>Deutsch (German)</a> <a class=dropdown-item href=/hi/docs/tutorials/>हिन्दी (Hindi)</a> <a class=dropdown-item href=/id/docs/tutorials/>Bahasa Indonesia (Indonesian)</a> <a class=dropdown-item href=/it/docs/tutorials/>Italiano (Italian)</a> <a class=dropdown-item href=/ja/docs/tutorials/>日本語 (Japanese)</a> <a class=dropdown-item href=/ko/docs/tutorials/>한국어 (Korean)</a> <a class=dropdown-item href=/pl/docs/tutorials/>Polski (Polish)</a> <a class=dropdown-item href=/pt-br/docs/tutorials/>Português (Portuguese)</a> <a class=dropdown-item href=/ru/docs/tutorials/>Русский (Russian)</a> <a class=dropdown-item href=/es/docs/tutorials/>Español (Spanish)</a> <a class=dropdown-item href=/uk/docs/tutorials/>Українська (Ukrainian)</a></div></li><li class="search-item nav-item mr-n4 mr-lg-0"><div class=search-bar><i class="search-icon fas fa-search"></i> <input type=search name=q data-search-page=/zh-cn/search/ class="search-input td-search-input" placeholder=搜索 aria-label=搜索 autocomplete=off></div></li></ul></div><button id=hamburger onclick=kub.toggleMenu() data-auto-burger-exclude><div></div></button></nav></header><div class="container-fluid td-outer"><div class=td-main><div class="row flex-xl-nowrap"><main class="col-12 col-md-9 col-xl-8 pl-md-5" role=main><div class=td-content><div class="pageinfo pageinfo-primary d-print-none"><p>这是本节的多页打印视图。 <a href=# onclick="return print(),!1">点击此处打印</a>.</p><p><a href=/zh-cn/docs/tutorials/>返回本页常规视图</a>.</p></div><h1 class=title>教程</h1><ul><li>1: <a href=#pg-5e3051fff9e84735871d9fb5e7b93f33>你好,Minikube</a></li><li>2: <a href=#pg-3c83f53a74233ace9b289ac5e24c3e62>学习 Kubernetes 基础知识</a></li><ul><li>2.1: <a href=#pg-7df66040311338d6098ebeab43ba9afb>创建集群</a></li><ul><li>2.1.1: <a href=#pg-de49316920e97a82e36763cb66781ada>使用 Minikube 创建集群</a></li></ul><li>2.2: <a href=#pg-76d78b3fba507f7ed33cef14a35b631d>部署应用</a></li><ul><li>2.2.1: <a href=#pg-2b1bba431989008c7493109a0f049ece>使用 kubectl 创建 Deployment</a></li></ul><li>2.3: <a href=#pg-250d620a73ec8be7e1f7d835574c4596>了解你的应用</a></li><ul><li>2.3.1: <a href=#pg-2771f4e8c45321b17cb0114a2d266453>查看 Pod 和节点</a></li></ul><li>2.4: <a href=#pg-4b0e31c9e0eae68bbb0a358b4042ada9>公开地暴露你的应用</a></li><ul><li>2.4.1: <a href=#pg-8ef4dad8f743b191a9e8c6f891cb191a>使用 Service 暴露你的应用</a></li></ul><li>2.5: <a href=#pg-be4996c93fb39c459a30b6669569d423>扩缩你的应用</a></li><ul><li>2.5.1: <a href=#pg-d1c15c9bd4f625adbc13149b1475287c>运行多实例的应用</a></li></ul><li>2.6: <a href=#pg-62b8b17dadfb55f1801cf8439e944e58>更新你的应用</a></li><ul><li>2.6.1: <a href=#pg-12e04355145afad615ca3c38335ba019>执行滚动更新</a></li></ul></ul><li>3: <a href=#pg-a3a0f1c6af19fc89ce24d8cd42c0249f>配置</a></li><ul><li>3.1: <a href=#pg-b8268dd408000835b485de3a4f3343ab>通过 ConfigMap 更新配置</a></li><li>3.2: <a href=#pg-2efe621cc085b350c8c4574e6f7f1311>使用 ConfigMap 来配置 Redis</a></li><li>3.3: <a href=#pg-f7dcafe033a10939f2a0291617626575>使用边车(Sidecar)容器</a></li></ul><li>4: <a href=#pg-fe7e92bed8fb92872b139f12c4568cdb>安全</a></li><ul><li>4.1: <a href=#pg-d5f847bcdb6f7efbfc9c8a180d73e29a>在集群级别应用 Pod 安全标准</a></li><li>4.2: <a href=#pg-31a6c137cfc5bfea9d88f4b109109465>在名字空间级别应用 Pod 安全标准</a></li><li>4.3: <a href=#pg-fca078b8ac6b82352ed52187a2da91b7>使用 AppArmor 限制容器对资源的访问</a></li><li>4.4: <a href=#pg-8b105172a11322c70d0223bc9dff1904>使用 seccomp 限制容器的系统调用</a></li></ul><li>5: <a href=#pg-1efbbc2c3015389f835b1661d5effb29>无状态的应用</a></li><ul><li>5.1: <a href=#pg-62caf420877232190a7404b8d93c6724>公开外部 IP 地址以访问集群中的应用</a></li><li>5.2: <a href=#pg-8c56795c6614cc5f52434ecc756448ac>示例:使用 Redis 部署 PHP 留言板应用</a></li></ul><li>6: <a href=#pg-d6336d9712aa433eb5f0fb8cbed6bef7>有状态的应用</a></li><ul><li>6.1: <a href=#pg-42e39658021b706bcc9478c8cc73c4a3>StatefulSet 基础</a></li><li>6.2: <a href=#pg-27580b3f65f3c2da07fc0f83be69da75>示例:使用持久卷部署 WordPress 和 MySQL</a></li><li>6.3: <a href=#pg-bf0d8e08fddd6e0282709b9fef8b5f67>示例:使用 StatefulSet 部署 Cassandra</a></li><li>6.4: <a href=#pg-4bfac214b5eb9ebddaf1f3811901d327>运行 ZooKeeper,一个分布式协调系统</a></li></ul><li>7: <a href=#pg-f4c0cdc5efc0b99d834a7ed2753ed1eb>集群管理</a></li><ul><li>7.1: <a href=#pg-d4134e0b428e3d8466977e543f95303d>以独立模式运行 kubelet</a></li></ul><li>8: <a href=#pg-97489f0aa8ac2df31a0d6b444a7bde62>Service</a></li><ul><li>8.1: <a href=#pg-bc0a2760d2865e91c501bc2467cd1a4b>使用 Service 连接到应用</a></li><li>8.2: <a href=#pg-5642e8c51749e4fe2e6a2ccc207f1fab>使用源 IP</a></li><li>8.3: <a href=#pg-3cecc68ef365a9d2ee0b4860dc74cacc>探索 Pod 及其端点的终止行为</a></li></ul></ul><div class=content><p>Kubernetes 文档的这一部分包含教程。 每个教程展示了如何完成一个比单个<a href=/zh-cn/docs/tasks/>任务</a>更大的目标。 通常一个教程有几个部分,每个部分都有一系列步骤。在浏览每个教程之前, 你可能希望将<a href=/zh-cn/docs/reference/glossary/>标准化术语表</a>页面添加到书签,供以后参考。</p><h2 id=basics>基础知识</h2><ul><li><a href=/zh-cn/docs/tutorials/Kubernetes-Basics/>Kubernetes 基础知识</a> 是一个深入的交互式教程,帮助你理解 Kubernetes 系统,并尝试一些基本的 Kubernetes 特性。</li><li><a href=https://www.edx.org/course/introduction-kubernetes-linuxfoundationx-lfs158x>Kubernetes 介绍 (edX)</a></li><li><a href=/zh-cn/docs/tutorials/hello-minikube/>你好 Minikube</a></li></ul><h2 id=configuration>配置</h2><ul><li><a href=/zh-cn/docs/tutorials/configuration/configure-java-microservice/>示例:配置 Java 微服务</a></li><li><a href=/zh-cn/docs/tutorials/configuration/configure-redis-using-configmap/>使用 ConfigMap 配置 Redis</a></li></ul><h2 id=构造-pod>构造 Pod</h2><ul><li><a href=/zh-cn/docs/tutorials/configuration/pod-sidecar-containers/>采用 Sidecar 容器</a></li></ul><h2 id=stateless-applications>无状态应用程序</h2><ul><li><a href=/zh-cn/docs/tutorials/stateless-application/expose-external-ip-address/>公开外部 IP 地址访问集群中的应用程序</a></li><li><a href=/zh-cn/docs/tutorials/stateless-application/guestbook/>示例:使用 Redis 部署 PHP 留言板应用程序</a></li></ul><h2 id=stateful-applications>有状态应用程序</h2><ul><li><a href=/zh-cn/docs/tutorials/stateful-application/basic-stateful-set/>StatefulSet 基础</a></li><li><a href=/zh-cn/docs/tutorials/stateful-application/mysql-wordpress-persistent-volume/>示例:WordPress 和 MySQL 使用持久卷</a></li><li><a href=/zh-cn/docs/tutorials/stateful-application/cassandra/>示例:使用有状态集部署 Cassandra</a></li><li><a href=/zh-cn/docs/tutorials/stateful-application/zookeeper/>运行 ZooKeeper,CP 分布式系统</a></li></ul><h2 id=services>服务</h2><ul><li><a href=/zh-cn/docs/tutorials/services/connect-applications-service/>使用 Service 连接到应用</a></li><li><a href=/zh-cn/docs/tutorials/services/source-ip/>使用源 IP</a></li></ul><h2 id=security>安全</h2><ul><li><a href=/zh-cn/docs/tutorials/security/cluster-level-pss/>在集群级别应用 Pod 安全标准</a></li><li><a href=/zh-cn/docs/tutorials/security/ns-level-pss/>在名字空间级别应用 Pod 安全标准</a></li><li><a href=/zh-cn/docs/tutorials/security/apparmor/>使用 AppArmor 限制容器对资源的访问</a></li><li><a href=/zh-cn/docs/tutorials/security/seccomp/>Seccomp</a></li></ul><h2 id=集群管理>集群管理</h2><ul><li><a href=/zh-cn/docs/tutorials/cluster-management/kubelet-standalone/>以独立模式运行 kubelet</a></li></ul><h2 id=接下来>接下来</h2><p>如果你要编写教程,请参阅<a href=/zh-cn/docs/contribute/style/page-content-types/>内容页面类型</a> 以获取有关教程页面类型的信息。</p></div></div><div class=td-content style=page-break-before:always><h1 id=pg-5e3051fff9e84735871d9fb5e7b93f33>1 - 你好,Minikube</h1><p>本教程向你展示如何使用 Minikube 在 Kubernetes 上运行一个应用示例。 教程提供了容器镜像,使用 NGINX 来对所有请求做出回应。</p><h2 id=教程目标>教程目标</h2><ul><li>将一个示例应用部署到 Minikube。</li><li>运行应用程序。</li><li>查看应用日志。</li></ul><h2 id=准备开始>准备开始</h2><p>本教程假设你已经安装了 <code>minikube</code>。 有关安装说明,请参阅 <a href=https://minikube.sigs.k8s.io/docs/start/>minikube start</a> 的<strong>步骤 1</strong>。</p><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>仅执行<strong>步骤 1:安装</strong>中的说明,其余内容均包含在本页中。</p></div><p>你还需要安装 <code>kubectl</code>。 有关安装说明,请参阅<a href=/zh-cn/docs/tasks/tools/#kubectl>安装工具</a>。</p><h2 id=create-a-minikube-cluster>创建 Minikube 集群</h2><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>minikube start </span></span></code></pre></div><h2 id=open-the-dashboard>打开仪表板</h2><p>打开 Kubernetes 仪表板。你可以通过两种不同的方式执行此操作:</p><ul class="nav nav-tabs" id=dashboard role=tablist><li class=nav-item><a data-toggle=tab class="nav-link active" href=#dashboard-0 role=tab aria-controls=dashboard-0 aria-selected=true>启动浏览器</a></li><li class=nav-item><a data-toggle=tab class=nav-link href=#dashboard-1 role=tab aria-controls=dashboard-1>URL 复制粘贴</a></li></ul><div class=tab-content id=dashboard><div id=dashboard-0 class="tab-pane show active" role=tabpanel aria-labelledby=dashboard-0><p><p>打开一个<strong>新的</strong>终端,然后运行:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 启动一个新的终端,并保持此命令运行。</span> </span></span><span style=display:flex><span>minikube dashboard </span></span></code></pre></div><p>现在,切换回运行 <code>minikube start</code> 的终端。</p><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p><code>dashboard</code> 命令启用仪表板插件,并在默认的 Web 浏览器中打开代理。 你可以在仪表板上创建 Kubernetes 资源,例如 Deployment 和 Service。</p><p>要了解如何避免从终端直接调用浏览器并获取 Web 仪表板的 URL,请参阅 "URL 复制和粘贴"选项卡。</p><p>默认情况下,仪表板只能从内部 Kubernetes 虚拟网络中访问。 <code>dashboard</code> 命令创建一个临时代理,使仪表板可以从 Kubernetes 虚拟网络外部访问。</p><p>要停止代理,请运行 <code>Ctrl+C</code> 退出该进程。仪表板仍在运行中。 命令退出后,仪表板仍然在 Kubernetes 集群中运行。 你可以再次运行 <code>dashboard</code> 命令创建另一个代理来访问仪表板。</p></div></div><div id=dashboard-1 class=tab-pane role=tabpanel aria-labelledby=dashboard-1><p><p>如果你不想 Minikube 为你打开 Web 浏览器,可以使用 <code>--url</code> 标志运行 <code>dashboard</code> 子命令。 <code>minikube</code> 会输出一个 URL,你可以在你喜欢的浏览器中打开该 URL。</p><p>打开一个<strong>新的</strong>终端,然后运行:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 启动一个新的终端,并保持此命令运行。</span> </span></span><span style=display:flex><span>minikube dashboard --url </span></span></code></pre></div><p>现在,你可以使用此 URL 并切换回运行 <code>minikube start</code> 的终端。</p></div></div><h2 id=create-a-deployment>创建 Deployment</h2><p>Kubernetes <a href=/zh-cn/docs/concepts/workloads/pods/><strong>Pod</strong></a> 是由一个或多个为了管理和联网而绑定在一起的容器构成的组。本教程中的 Pod 只有一个容器。 Kubernetes <a href=/zh-cn/docs/concepts/workloads/controllers/deployment/><strong>Deployment</strong></a> 检查 Pod 的健康状况,并在 Pod 中的容器终止的情况下重新启动新的容器。 Deployment 是管理 Pod 创建和扩展的推荐方法。</p><ol><li><p>使用 <code>kubectl create</code> 命令创建管理 Pod 的 Deployment。该 Pod 根据提供的 Docker 镜像运行容器。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 运行包含 Web 服务器的测试容器镜像</span> </span></span><span style=display:flex><span>kubectl create deployment hello-node --image<span style=color:#666>=</span>registry.k8s.io/e2e-test-images/agnhost:2.39 -- /agnhost netexec --http-port<span style=color:#666>=</span><span style=color:#666>8080</span> </span></span></code></pre></div></li></ol><ol start=2><li><p>查看 Deployment:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get deployments </span></span></code></pre></div><p>输出结果类似于这样:</p><pre tabindex=0><code>NAME READY UP-TO-DATE AVAILABLE AGE hello-node 1/1 1 1 1m </code></pre><p>(该 Pod 可能需要一些时间才能变得可用。如果你在输出结果中看到 “0/1”,请在几秒钟后重试。)</p></li></ol><ol start=3><li><p>查看 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods </span></span></code></pre></div><p>输出结果类似于这样:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE hello-node-5f76cf6ccf-br9b5 1/1 Running 0 1m </code></pre></li></ol><ol start=4><li><p>查看集群事件:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get events </span></span></code></pre></div></li></ol><ol start=5><li><p>查看 <code>kubectl</code> 配置:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl config view </span></span></code></pre></div></li></ol><ol start=6><li><p>查看 Pod 中容器的应用程序日志(将 Pod 名称替换为你用 <code>kubectl get pods</code> 命令获得的名称)。</p><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>将 <code>kubectl logs</code> 命令中的 <code>hello-node-5f76cf6ccf-br9b5</code> 替换为 <code>kubectl get pods</code> 命令输出中的 Pod 名称。</p></div><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl logs hello-node-5f76cf6ccf-br9b5 </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>I0911 09:19:26.677397 1 log.go:195] Started HTTP server on port 8080 I0911 09:19:26.677586 1 log.go:195] Started UDP server on port 8081 </code></pre></li></ol><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>有关 <code>kubectl</code> 命令的更多信息,请参阅 <a href=/zh-cn/docs/reference/kubectl/>kubectl 概述</a>。</p></div><h2 id=create-a-service>创建 Service</h2><p>默认情况下,Pod 只能通过 Kubernetes 集群中的内部 IP 地址访问。 要使得 <code>hello-node</code> 容器可以从 Kubernetes 虚拟网络的外部访问,你必须将 Pod 通过 Kubernetes <a href=/zh-cn/docs/concepts/services-networking/service/><strong>Service</strong></a> 公开出来。</p><div class="alert alert-danger" role=alert><h4 class=alert-heading>警告:</h4><p>agnhost 容器有一个 <code>/shell</code> 端点,对于调试很有帮助,但暴露给公共互联网很危险。 请勿在面向互联网的集群或生产集群上运行它。</p></div><ol><li><p>使用 <code>kubectl expose</code> 命令将 Pod 暴露给公网:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl expose deployment hello-node --type<span style=color:#666>=</span>LoadBalancer --port<span style=color:#666>=</span><span style=color:#666>8080</span> </span></span></code></pre></div><p>这里的 <code>--type=LoadBalancer</code> 参数表明你希望将你的 Service 暴露到集群外部。</p><p>测试镜像中的应用程序代码仅监听 TCP 8080 端口。 如果你用 <code>kubectl expose</code> 暴露了其它的端口,客户端将不能访问其它端口。</p></li></ol><ol start=2><li><p>查看你创建的 Service:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get services </span></span></code></pre></div><p>输出结果类似于这样:</p><pre tabindex=0><code>NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE hello-node LoadBalancer 10.108.144.78 <pending> 8080:30369/TCP 21s kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 23m </code></pre><p>对于支持负载均衡器的云服务平台而言,平台将提供一个外部 IP 来访问该服务。 在 Minikube 上,<code>LoadBalancer</code> 使得服务可以通过命令 <code>minikube service</code> 访问。</p></li></ol><ol start=3><li><p>运行下面的命令:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>minikube service hello-node </span></span></code></pre></div><p>这将打开一个浏览器窗口,为你的应用程序提供服务并显示应用的响应。</p></li></ol><h2 id=enable-addons>启用插件</h2><p>Minikube 有一组内置的<a class=glossary-tooltip title='扩展 Kubernetes 功能的资源。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/cluster-administration/addons/ target=_blank aria-label=插件>插件</a>, 可以在本地 Kubernetes 环境中启用、禁用和打开。</p><ol><li><p>列出当前支持的插件:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>minikube addons list </span></span></code></pre></div><p>输出结果类似于这样:</p><pre tabindex=0><code>addon-manager: enabled dashboard: enabled default-storageclass: enabled efk: disabled freshpod: disabled gvisor: disabled helm-tiller: disabled ingress: disabled ingress-dns: disabled logviewer: disabled metrics-server: disabled nvidia-driver-installer: disabled nvidia-gpu-device-plugin: disabled registry: disabled registry-creds: disabled storage-provisioner: enabled storage-provisioner-gluster: disabled </code></pre></li></ol><ol start=2><li><p>启用插件,例如 <code>metrics-server</code>:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>minikube addons <span style=color:#a2f>enable</span> metrics-server </span></span></code></pre></div><p>输出结果类似于这样:</p><pre tabindex=0><code>The 'metrics-server' addon is enabled </code></pre></li></ol><ol start=3><li><p>查看通过安装该插件所创建的 Pod 和 Service:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pod,svc -n kube-system </span></span></code></pre></div><p>输出结果类似于这样:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE pod/coredns-5644d7b6d9-mh9ll 1/1 Running 0 34m pod/coredns-5644d7b6d9-pqd2t 1/1 Running 0 34m pod/metrics-server-67fb648c5 1/1 Running 0 26s pod/etcd-minikube 1/1 Running 0 34m pod/influxdb-grafana-b29w8 2/2 Running 0 26s pod/kube-addon-manager-minikube 1/1 Running 0 34m pod/kube-apiserver-minikube 1/1 Running 0 34m pod/kube-controller-manager-minikube 1/1 Running 0 34m pod/kube-proxy-rnlps 1/1 Running 0 34m pod/kube-scheduler-minikube 1/1 Running 0 34m pod/storage-provisioner 1/1 Running 0 34m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/metrics-server ClusterIP 10.96.241.45 <none> 80/TCP 26s service/kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP 34m service/monitoring-grafana NodePort 10.99.24.54 <none> 80:30002/TCP 26s service/monitoring-influxdb ClusterIP 10.111.169.94 <none> 8083/TCP,8086/TCP 26s </code></pre></li></ol><ol start=4><li><p>检查 <code>metrics-server</code> 的输出:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl top pods </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>NAME CPU(cores) MEMORY(bytes) hello-node-ccf4b9788-4jn97 1m 6Mi </code></pre><p>如果你看到以下消息,请等待并重试:</p><pre tabindex=0><code>error: Metrics API not available </code></pre></li></ol><ol start=5><li><p>禁用 <code>metrics-server</code>:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>minikube addons disable metrics-server </span></span></code></pre></div><p>输出结果类似于这样:</p><pre tabindex=0><code>metrics-server was successfully disabled </code></pre></li></ol><h2 id=clean-up>清理</h2><p>现在可以清理你在集群中创建的资源:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete service hello-node </span></span><span style=display:flex><span>kubectl delete deployment hello-node </span></span></code></pre></div><p>停止 Minikube 集群:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>minikube stop </span></span></code></pre></div><p>可选地,删除 Minikube 虚拟机(VM):</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 可选的</span> </span></span><span style=display:flex><span>minikube delete </span></span></code></pre></div><p>如果你还想使用 Minikube 进一步学习 Kubernetes,那就不需要删除 Minikube。</p><h2 id=结论>结论</h2><p>本页介绍了启动和运行 minikube 集群的基本知识,现在部署应用的准备工作已经完成。</p><h2 id=接下来>接下来</h2><ul><li><a href=/zh-cn/docs/tutorials/kubernetes-basics/deploy-app/deploy-intro/>使用 kubectl 在 Kubernetes 上部署你的第一个应用程序</a>教程。</li><li>进一步了解 <a href=/zh-cn/docs/concepts/workloads/controllers/deployment/>Deployment 对象</a>。</li><li>进一步了解<a href=/zh-cn/docs/tasks/run-application/run-stateless-application-deployment/>部署应用</a>。</li><li>进一步了解 <a href=/zh-cn/docs/concepts/services-networking/service/>Service 对象</a>。</li></ul></div><div class=td-content style=page-break-before:always><h1 id=pg-3c83f53a74233ace9b289ac5e24c3e62>2 - 学习 Kubernetes 基础知识</h1><!doctype html><html lang=zh><body><div class=layout id=top><main class=content><div class=row><div class=col-md-9><h2>Kubernetes 基础</h2><p>本教程介绍了 Kubernetes 集群编排系统的基础知识。每个模块包含关于 Kubernetes 主要特性和概念的一些背景信息,并包括一个在线互动教程。这些互动教程让你可以自己管理一个简单的集群及其容器化应用程序。</p><p>使用互动教程,你可以学习:</p><ul><li>在集群上部署容器化应用程序</li><li>弹性部署</li><li>使用新的软件版本,更新容器化应用程序</li><li>调试容器化应用程序</li></ul><p>这些教程使用 Katacoda 在你的浏览器中运行一个运行着 Minikube 的虚拟终端,Minikube 是 Kubernetes 的一个小规模本地部署,可以运行在任何地方。不需要安装任何软件或进行任何配置;每个交互性教程都直接从你的网页浏览器上运行。</p></div></div><br><div class=row><div class=col-md-9><h2>Kubernetes 可以为你做些什么?</h2><p>通过现代的 Web 服务,用户希望应用程序能够 24/7 全天候使用,开发人员希望每天可以多次发布部署新版本的应用程序。 容器化可以帮助软件包达成这些目标,使应用程序能够以简单快速的方式发布和更新,而无需停机。Kubernetes 帮助你确保这些容器化的应用程序在你想要的时间和地点运行,并帮助应用程序找到它们需要的资源和工具。Kubernetes 是一个可用于生产的开源平台,根据 Google 容器集群方面积累的经验,以及来自社区的最佳实践而设计。</p></div></div><br><div id=basics-modules class=content__modules><h2>Kubernetes 基础模块</h2><div class=row><div class=col-md-12><div class=row><div class=col-md-4><div class=thumbnail><a href=/zh-cn/docs/tutorials/kubernetes-basics/create-cluster/cluster-intro/><img src="/docs/tutorials/kubernetes-basics/public/images/module_01.svg?v=1469803628347" alt></a><div class=caption><a href=/zh-cn/docs/tutorials/kubernetes-basics/create-cluster/cluster-intro/><h5>1. 创建一个 Kubernetes 集群</h5></a></div></div></div><div class=col-md-4><div class=thumbnail><a href=/zh-cn/docs/tutorials/kubernetes-basics/deploy-app/deploy-intro/><img src="/docs/tutorials/kubernetes-basics/public/images/module_02.svg?v=1469803628347" alt></a><div class=caption><a href=/zh-cn/docs/tutorials/kubernetes-basics/deploy-app/deploy-intro/><h5>2. 部署应用程序</h5></a></div></div></div><div class=col-md-4><div class=thumbnail><a href=/zh-cn/docs/tutorials/kubernetes-basics/explore/explore-intro/><img src="/docs/tutorials/kubernetes-basics/public/images/module_03.svg?v=1469803628347" alt></a><div class=caption><a href=/zh-cn/docs/tutorials/kubernetes-basics/explore/explore-intro/><h5>3. 应用程序探索</h5></a></div></div></div></div></div><div class=col-md-12><div class=row><div class=col-md-4><div class=thumbnail><a href=/zh-cn/docs/tutorials/kubernetes-basics/expose/expose-intro/><img src="/docs/tutorials/kubernetes-basics/public/images/module_04.svg?v=1469803628347" alt></a><div class=caption><a href=/zh-cn/docs/tutorials/kubernetes-basics/expose/expose-intro/><h5>4. 应用外部可见</h5></a></div></div></div><div class=col-md-4><div class=thumbnail><a href=/zh-cn/docs/tutorials/kubernetes-basics/scale/scale-intro/><img src="/docs/tutorials/kubernetes-basics/public/images/module_05.svg?v=1469803628347" alt></a><div class=caption><a href=/zh-cn/docs/tutorials/kubernetes-basics/scale/scale-intro/><h5>5. 应用可扩展</h5></a></div></div></div><div class=col-md-4><div class=thumbnail><a href=/zh-cn/docs/tutorials/kubernetes-basics/update/update-intro/><img src="/docs/tutorials/kubernetes-basics/public/images/module_06.svg?v=1469803628347" alt></a><div class=caption><a href=/zh-cn/docs/tutorials/kubernetes-basics/update/update-intro/><h5>6. 应用更新</h5></a></div></div></div></div></div></div></div></main></div></body></html></div><div class=td-content style=page-break-before:always><h1 id=pg-7df66040311338d6098ebeab43ba9afb>2.1 - 创建集群</h1><p>了解 Kubernetes <a class=glossary-tooltip title=一组工作机器,称为节点,会运行容器化应用程序。每个集群至少有一个工作节点。 data-toggle=tooltip data-placement=top href='/zh-cn/docs/reference/glossary/?all=true#term-cluster' target=_blank aria-label=集群>集群</a>并使用 Minikube 创建一个简单的集群。</p></div><div class=td-content><h1 id=pg-de49316920e97a82e36763cb66781ada>2.1.1 - 使用 Minikube 创建集群</h1><div class=lead>了解 Kubernetes 集群。 了解 Minikube。 启动一个 Kubernetes 集群。</div><!doctype html><html lang=zh><body><div class=layout id=top><main class=content><div class=row><div class=col-md-8><h3>目标</h3><ul><li>了解 Kubernetes 集群。</li><li>了解 Minikube。</li><li>在你的电脑上开启一个 Kubernetes 集群。</li></ul></div><div class=col-md-8><h3>Kubernetes 集群</h3><p><b>Kubernetes 协调一个高可用计算机集群,每个计算机作为独立单元互相连接工作。</b> Kubernetes 中的抽象允许你将容器化的应用部署到集群,而无需将它们绑定到某个特定的独立计算机。 为了使用这种新的部署模型,应用需要以将应用与单个主机分离的方式打包:它们需要被容器化。 与过去的那种应用直接以包的方式深度与主机集成的部署模型相比,容器化应用更灵活、更可用。 <b>Kubernetes 以更高效的方式跨集群自动分发和调度应用容器。</b> Kubernetes 是一个开源平台,并且可应用于生产环境。</p><p>一个 Kubernetes 集群包含两种类型的资源:<ul><li><b>控制面</b>调度整个集群</li><li><b>节点(Nodes)</b>负责运行应用</li></ul></p></div><div class=col-md-4><div class="content__box content__box_lined"><h3>总结:</h3><ul><li>Kubernetes 集群</li><li>Minikube</li></ul></div><div class="content__box content__box_fill"><p><i>Kubernetes 是一个生产级别的开源平台,可协调在计算机集群内和跨计算机集群的应用容器的部署(调度)和执行.</i></p></div></div></div><br><div class=row><div class=col-md-8><h2 style=color:#3771e3>集群图</h2></div></div><div class=row><div class=col-md-8><p><img src=/docs/tutorials/kubernetes-basics/public/images/module_01_cluster.svg></p></div></div><br><div class=row><div class=col-md-8><p><b>控制面负责管理整个集群。</b> 控制面协调集群中的所有活动,例如调度应用、维护应用的所需状态、应用扩容以及推出新的更新。</p><p><b>节点是一个虚拟机或者物理机,它在 Kubernetes 集群中充当工作机器的角色。</b> 每个节点都有 Kubelet,它管理节点而且是节点与控制面通信的代理。 节点还应该具有用于处理容器操作的工具,例如 <a class=glossary-tooltip title=强调简单性、健壮性和可移植性的一种容器运行时 data-toggle=tooltip data-placement=top href=https://containerd.io/docs/ target=_blank aria-label=containerd>containerd</a> 或 <a class=glossary-tooltip title='专用于 Kubernetes 的轻量级容器运行时软件' data-toggle=tooltip data-placement=top href=https://cri-o.io/#what-is-cri-o target=_blank aria-label=CRI-O>CRI-O</a>。 处理生产级流量的 Kubernetes 集群至少应具有三个节点,因为如果只有一个节点,出现故障时其对应的 <a href=/zh-cn/docs/concepts/architecture/#etcd>etcd</a> 成员和控制面实例都会丢失, 并且冗余会受到影响。你可以通过添加更多控制面节点来降低这种风险。</p></div><div class=col-md-4><div class="content__box content__box_fill"><p><i>控制面管理集群,节点用于托管正在运行的应用。</i></p></div></div></div><div class=row><div class=col-md-8><p>在 Kubernetes 上部署应用时,你告诉控制面启动应用容器。 控制面就编排容器在集群的节点上运行。 <b>节点使用控制面暴露的 <a href=/zh-cn/docs/concepts/overview/kubernetes-api/>Kubernetes API</a> 与控制面通信。</b>终端用户也可以使用 Kubernetes API 与集群交互。</p><p>Kubernetes 既可以部署在物理机上也可以部署在虚拟机上。你可以使用 Minikube 开始部署 Kubernetes 集群。 Minikube 是一种轻量级的 Kubernetes 实现,可在本地计算机上创建 VM 并部署仅包含一个节点的简单集群。 Minikube 可用于 Linux、macOS 和 Windows 系统。Minikube CLI 提供了用于引导集群工作的多种操作, 包括启动、停止、查看状态和删除。</p><p>现在你已经知道 Kubernetes 是什么了,在你的电脑上访问<a href=/zh-cn/docs/tutorials/hello-minikube/>你好 Minikube</a>, 试试。</p></div></div><div class=row><p>完成这一步之后,继续<a href=/zh-cn/docs/tutorials/kubernetes-basics/deploy-app/deploy-intro/>使用 <tt>kubectl</tt> 创建一个 Deployment</a>。</p></div></main></div></body></html></div><div class=td-content style=page-break-before:always><h1 id=pg-76d78b3fba507f7ed33cef14a35b631d>2.2 - 部署应用</h1></div><div class=td-content><h1 id=pg-2b1bba431989008c7493109a0f049ece>2.2.1 - 使用 kubectl 创建 Deployment</h1><div class=lead>学习应用的部署。 使用 kubectl 在 Kubernetes 上部署第一个应用。</div><!doctype html><html lang=zh><body><link href=/docs/tutorials/kubernetes-basics/public/css/styles.css rel=stylesheet><div class=layout id=top><main class=content><div class=row><div class=col-md-8><h3>目标</h3><ul><li>学习应用的部署。</li><li>使用 kubectl 在 Kubernetes 上部署第一个应用。</li></ul></div><div class=col-md-8><h3>Kubernetes 部署</h3><p>一旦<a href=/zh-cn/docs/tutorials/kubernetes-basics/create-cluster/cluster-intro/>运行了 Kubernetes 集群</a>, 就可以在其上部署容器化应用。为此,你需要创建 Kubernetes <b>Deployment</b>。 Deployment 指挥 Kubernetes 如何创建和更新应用的实例。 创建 Deployment 后,Kubernetes 控制平面将 Deployment 中包含的应用实例调度到集群中的各个节点上。</p><p>创建应用实例后,Kubernetes Deployment 控制器会持续监视这些实例。 如果托管实例的节点关闭或被删除,则 Deployment 控制器会将该实例替换为集群中另一个节点上的实例。 <b>这提供了一种自我修复机制来解决机器故障维护问题。</b></p><p>在没有 Kubernetes 这种编排系统之前,安装脚本通常用于启动应用,但它们不允许从机器故障中恢复。 通过创建应用实例并使它们在节点之间运行,Kubernetes Deployment 提供了一种与众不同的应用管理方法。</p></div><div class=col-md-4><div class="content__box content__box_lined"><h3>总结:</h3><ul><li>Deployment</li><li>kubectl</li></ul></div><div class="content__box content__box_fill"><p><i>Deployment 负责创建和更新应用的实例</i></p></div></div></div><br><div class=row><div class=col-md-8><h2 style=color:#3771e3>部署你在 Kubernetes 上的第一个应用</h2></div></div><div class=row><div class=col-md-8><p><img src=/docs/tutorials/kubernetes-basics/public/images/module_02_first_app.svg></p></div></div><br><div class=row><div class=col-md-8><p>你可以使用 Kubernetes 命令行界面 <b>kubectl</b> 创建和管理 Deployment。 kubectl 使用 Kubernetes API 与集群进行交互。在本单元中,你将学习创建在 Kubernetes 集群上运行应用的 Deployment 所需的最常见的 kubectl 命令。</p><p>创建 Deployment 时,你需要指定应用的容器镜像以及要运行的副本数。 你可以稍后通过更新 Deployment 来更改该信息; 模块 <a href=/zh-cn/docs/tutorials/kubernetes-basics/scale/scale-intro/>5</a> 和 <a href=/zh-cn/docs/tutorials/kubernetes-basics/update/update-intro/>6</a> 讨论了如何扩展和更新 Deployment。</p></div><div class=col-md-4><div class="content__box content__box_fill"><p><i>应用需要打包成一种受支持的容器格式,以便部署在 Kubernetes 上</i></p></div></div></div><div class=row><div class=col-md-8><p>对于你第一次部署,你将使用打包在 Docker 容器中的 hello-node 应用,该应用使用 NGINX 回显所有请求。 (如果你尚未尝试创建 hello-node 应用并使用容器进行部署,则可以首先按照 <a href=/zh-cn/docs/tutorials/hello-minikube/>Hello Minikube 教程</a>中的说明进行操作)。<p>你也需要安装好 kubectl。如果你需要安装 kubectl,参阅<a href=/zh-cn/docs/tasks/tools/#kubectl>安装工具</a>。</p><p>现在你已经了解了部署的内容,让我们部署第一个应用!</p></div></div><br><div class=row><div class=col-md-8><h3>kubectl 基础知识</h3><p>kubectl 命令的常见格式是:<code>kubectl <i>操作资源</i></code></p><p>这会对指定的<em>资源</em>(类似 <tt>node</tt> 或 <tt>deployment</tt>)执行指定的<em>操作</em>(类似 create、describe 或 delete)。 你可以在子命令之后使用 <code>-<span>-help</code> 获取可能参数相关的更多信息(例如:<code>kubectl get nodes --help</code>)。</p><p>通过运行 <b><code>kubectl version</code></b> 命令,查看 kubectl 是否被配置为与你的集群通信。</p><p>查验 kubectl 是否已安装,你能同时看到客户端和服务器版本。</p><p>要查看集群中的节点,运行 <b><code>kubectl get nodes</code></b> 命令。</p><p>你可以看到可用的节点。稍后 Kubernetes 将根据节点可用的资源选择在哪里部署应用。</p></div></div><div class=row><a id=deploy-an-app></a><div class=col-md-12><h3>部署一个应用</h3><p>让我们使用 <code>kubectl create deployment</code> 命令在 Kubernetes 上部署第一个应用。 我们需要提供 Deployment 命令以及应用镜像位置(包括托管在 Docker hub 之外的镜像的完整仓库地址)。</p><p><b><code>kubectl create deployment kubernetes-bootcamp --image=gcr.io/google-samples/kubernetes-bootcamp:v1</code></b></p><p>很好!你刚刚通过创建 Deployment 部署了第一个应用。这个过程中执行了以下一些操作:</p><ul><li>搜索应用实例可以运行的合适节点(我们只有一个可用的节点)</li><li>调度应用在此节点上运行</li><li>配置集群在需要时将实例重新调度到新的节点上</li></ul><p>要列出你的 Deployment,使用 <code>kubectl get deployments</code> 命令:</p><p><b><code>kubectl get deployments</code></b></p><p>我们看到有 1 个 Deployment 运行应用的单个实例。这个实例运行在节点上的一个容器内。</p></div></div><div class=row><div class=col-md-12><h3>查看应用</h3><p>在 Kubernetes 内运行的 <a href=/docs/concepts/workloads/pods/>Pod</a> 运行在一个私有的、隔离的网络上。 默认这些 Pod 可以从同一 Kubernetes 集群内的其他 Pod 和服务看到,但超出这个网络后则看不到。 当我们使用 <code>kubectl</code> 时,我们通过 API 端点交互与应用进行通信。</p><p>在 <a href=/zh-cn/docs/tutorials/kubernetes-basics/expose/>模块 4</a> 中我们将讲述如何将应用暴露到 Kubernetes 集群外的其他选项。 同样作为基础教程,我们不会在这里详细解释 <code>Pod</code> 是什么,后面的主题会介绍它。</p><p><code>kubectl proxy</code> 命令可以创建一个代理,将通信转发到集群范围的私有网络。 按下 Ctrl-C 此代理可以被终止,且在此代理运行期间不会显示任何输出。</p><p><strong>你需要打开第二个终端窗口来运行此代理。</strong></p><p><b><code>kubectl proxy</b></code><p>现在我们在主机(终端)和 Kubernetes 集群之间有一个连接。此代理能够从这些终端直接访问 API。</p><p>你可以看到通过代理端点托管的所有 API。 例如,我们可以使用以下 <code>curl</code> 命令直接通过 API 查询版本:</p><p><b><code>curl http://localhost:8001/version</code></b></p><div class="alert alert-info note callout" role=alert><strong>注:</strong>如果 Port 8001 不可访问,确保你上述启动的 <code>kubectl proxy</code> 运行在第二个终端中。</div><p>API 服务器将基于也能通过代理访问的 Pod 名称为每个 Pod 自动创建端点。</p><p>首先我们需要获取 Pod 名称,我们将存储到环境变量 <tt>POD_NAME</tt> 中:</p><p><b><code>export POD_NAME=$(kubectl get pods -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}')</code></b><br><b><code>echo Name of the Pod: $POD_NAME</code></b></p><p>你可以运行以下命令通过代理的 API 访问 Pod:</p><p><b><code>curl http://localhost:8001/api/v1/namespaces/default/pods/$POD_NAME/</code></b></p><p>为了不使用代理也能访问新的 Deployment,需要一个 Service,这将在下一个 <a href=/zh-cn/docs/tutorials/kubernetes-basics/expose/>模块 4</a> 中讲述。</p></div></div><div class=row><p>如果你准备好了,请访问<a href=/zh-cn/docs/tutorials/kubernetes-basics/explore/explore-intro/ title="查看 Pod 和 Node">查看 Pod 和 Node</a>。</p></p></div></main></div></body></html></div><div class=td-content style=page-break-before:always><h1 id=pg-250d620a73ec8be7e1f7d835574c4596>2.3 - 了解你的应用</h1></div><div class=td-content><h1 id=pg-2771f4e8c45321b17cb0114a2d266453>2.3.1 - 查看 Pod 和节点</h1><div class=lead>学习如何使用 kubectl get、kubectl describe、kubectl logs 和 kubectl exec 排除 Kubernetes 应用故障。</div><!doctype html><html lang=zh><body><div class=layout id=top><main class=content><div class=row><div class=col-md-8><h3>目标</h3><ul><li>了解 Kubernetes Pod。</li><li>了解 Kubernetes 节点。</li><li>对已部署的应用故障排除。</li></ul></div><div class=col-md-8><h2>Kubernetes Pod</h2><p>在模块 <a href=/zh-cn/docs/tutorials/kubernetes-basics/deploy-app/deploy-intro/>2</a> 中创建 Deployment 时, Kubernetes 创建了一个 <b>Pod</b> 来托管你的应用实例。Pod 是 Kubernetes 抽象出来的, 表示一组一个或多个应用容器(如 Docker),以及这些容器的一些共享资源。这些资源包括:</p><ul><li>共享存储,当作卷</li><li>网络,作为唯一的集群 IP 地址</li><li>有关每个容器如何运行的信息,例如容器镜像版本或要使用的特定端口</li></ul><p>Pod 为特定于应用的“逻辑主机”建模,并且可以包含相对紧耦合的不同应用容器。 例如,Pod 可能既包含带有 Node.js 应用的容器,也包含另一个不同的容器, 用于提供 Node.js 网络服务器要发布的数据。Pod 中的容器共享 IP 地址和端口, 始终位于同一位置并且共同调度,并在同一节点上的共享上下文中运行。</p><p>Pod 是 Kubernetes 平台上的原子单元。当我们在 Kubernetes 上创建 Deployment 时, 该 Deployment 会在其中创建包含容器的 Pod(而不是直接创建容器)。 每个 Pod 都与调度它的节点绑定,并保持在那里直到终止(根据重启策略)或删除。 如果节点发生故障,则会在集群中的其他可用节点上调度相同的 Pod。</p></div><div class=col-md-4><div class="content__box content__box_lined"><h3>总结:</h3><ul><li>Pod</li><li>节点</li><li>Kubectl 主要命令</li></ul></div><div class="content__box content__box_fill"><p><i>Pod 是一个或多个应用容器(例如 Docker)的组合,包括共享存储(卷)、IP 地址和有关如何运行它们的信息。</i></p></div></div></div><br><div class=row><div class=col-md-8><h2 style=color:#3771e3>Pod 概览</h2></div></div><div class=row><div class=col-md-8><p><img src=/docs/tutorials/kubernetes-basics/public/images/module_03_pods.svg></p></div></div><br><div class=row><div class=col-md-8><h2>节点</h2><p>Pod 总是运行在<b>节点</b>上。节点是 Kubernetes 中参与计算的机器,可以是虚拟机或物理计算机,具体取决于集群。 每个节点由控制面管理。节点可以有多个 Pod,Kubernetes 控制面会自动处理在集群中的节点上调度 Pod。 控制面的自动调度考量了每个节点上的可用资源。</p><p>每个 Kubernetes 节点至少运行:</p><ul><li>Kubelet,负责 Kubernetes 控制面和节点之间通信的进程;它管理机器上运行的 Pod 和容器。</li><li>容器运行时(如 Docker)负责从镜像仓库中提取容器镜像、解压缩容器以及运行应用。</li></ul></div><div class=col-md-4><div class="content__box content__box_fill"><p><i>如果容器紧耦合并且需要共享磁盘等资源,则只应将其编排在一个 Pod 中。</i></p></div></div></div><br><div class=row><div class=col-md-8><h2 style=color:#3771e3>节点概览</h2></div></div><div class=row><div class=col-md-8><p><img src=/docs/tutorials/kubernetes-basics/public/images/module_03_nodes.svg></p></div></div><br><div class=row><div class=col-md-8><h2>使用 kubectl 进行故障排除</h2><p>在模块 <a href=/zh-cn/docs/tutorials/kubernetes-basics/deploy-app/deploy-intro/>2</a> 中, 你使用了 kubectl 命令行界面。你将继续在第 3 个模块中使用 kubectl 来获取有关已部署应用及其环境的信息。 最常见的操作可以使用以下 kubectl 子命令完成:</p><ul><li><tt><b>kubectl get</b></tt> - 列出资源</li><li><tt><b>kubectl describe</b></tt> - 显示有关资源的详细信息</li><li><tt><b>kubectl logs</b></tt> - 打印 Pod 中容器的日志</li><li><tt><b>kubectl exec</b></tt> - 在 Pod 中的容器上执行命令</li></ul><p>你可以使用这些命令查看应用的部署时间、当前状态、运行位置以及配置。</p><p>现在我们了解了有关集群组件和命令行的更多信息,让我们来探索一下我们的应用。</p></div><div class=col-md-4><div class="content__box content__box_fill"><p><i>节点是 Kubernetes 中负责计算的机器,可能是虚拟机或物理计算机,具体取决于集群。 多个 Pod 可以在一个节点上运行。</i></p></div></div></div><div class=row><div class=col-md-12><h3>检查应用配置</h3><p>让我们验证之前场景中部署的应用是否在运行。我们将使用 <code>kubectl get</code> 命令查看现存的 Pod:</p><p><b><code>kubectl get pods</code></b></p><p>如果没有 Pod 在运行,请等几秒,让 Pod 再次列出。一旦看到一个 Pod 在运行,就可以继续操作。</p><p>接下来,要查看 Pod 内有哪些容器以及使用了哪些镜像来构建这些容器,我们运行 <code>kubectl describe pods</code> 命令:</p><p><b><code>kubectl describe pods</code></b></p><p>我们在这里看到了 Pod 的容器相关详情:IP 地址、使用的端口以及 Pod 生命期有关的事件列表。</p><p><tt>describe</tt> 子命令的输出宽泛,涵盖了一些我们还未讲到的概念,但不用担心, 这节课结束时你就会熟悉这些概念了。</p><p><em><strong>注:</strong><tt>describe</tt>子命令可用于获取有关大多数 Kubernetes 原语的详细信息, 包括节点、Pod 和 Deployment。describe 的输出设计为人类可读的信息,而不是脚本化的信息。</em></p></div></div><div class=row><div class=col-md-12><h3>在终端中显示应用</h3><p>回想一下,Pod 运行在隔离的、私有的网络中,因此我们需要代理访问它们,这样才能进行调试和交互。 为了做到这一点,我们将使用 <code>kubectl proxy</code> 命令在<strong>第二个终端</strong>中运行一个代理。 打开一个新的终端窗口,在这个新的终端中运行以下命令:</p><p><code><b>kubectl proxy</b></code></p><p>现在我们再次获取 Pod 命令并直接通过代理查询该 Pod。 要获取 Pod 命令并将其存到 <tt>POD_NAME</tt> 环境变量中,运行以下命令:</p><p><code><b>export POD_NAME="$(kubectl get pods -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}')"</b></code><br><code><b>echo Name of the Pod: $POD_NAME</b></code></p><p>要查看应用的输出,运行 <code>curl</code> 请求:</p><p><code><b>curl http://localhost:8001/api/v1/namespaces/default/pods/$POD_NAME:8080/proxy/</b></code></p><p>URL 是到 Pod API 的路由。</p></div></div><div class=row><div class=col-md-12><h3>查看容器日志</h3><p>应用通常发送到标准输出的所有内容都成为 Pod 内容器的日志。 我们可以使用 <code>kubectl logs</code> 命令检索这些日志:</p><p><code><b>kubectl logs "$POD_NAME"</b></code></p><p><em><strong>注:</strong>我们不需要指定容器名称,因为在 Pod 内只有一个容器。</em></p></div></div><div class=row><div class=col-md-12><h3>在容器上执行命令</h3><p>一旦 Pod 启动并运行,我们就可以直接在容器上执行命令。 为此,我们使用 <code>exec</code> 子命令,并将 Pod 的名称作为参数。让我们列出环境变量:</p><p><code><b>kubectl exec "$POD_NAME" -- env</b></code></p><p>另外值得一提的是,由于 Pod 中只有一个容器,所以容器本身的名称可以被省略。</p><p>接下来,让我们在 Pod 的容器中启动一个 bash 会话:</p><p><code><b>kubectl exec -ti $POD_NAME -- bash</b></code></p><p>现在我们在运行 Node.js 应用的容器上有一个打开的控制台。该应用的源代码位于 <tt>server.js</tt> 文件中:</p><p><code><b>cat server.js</b></code></p><p>你可以通过运行 <tt>curl</tt> 命令查看应用是否启动:</p><p><code><b>curl http://localhost:8080</b></code></p><p><em><strong>注:</strong>在这里我们使用了 <tt>localhost</tt>,因为我们在 NodeJS Pod 内执行了此命令。 如果你无法连接到 localhost:8080,请确保你已经运行了 <code>kubectl exec</code> 命令,并且从 Pod 内启动了该命令。</em></p><p>要关闭你的容器连接,键入 <code><b>exit</b></code>。</p></div></div><div class=row><p>如果你准备好了,请继续学习<a href=/zh-cn/docs/tutorials/kubernetes-basics/expose/expose-intro/ title=使用服务来暴露你的应用>使用服务来暴露你的应用</a>。</p></div></main></div></body></html></div><div class=td-content style=page-break-before:always><h1 id=pg-4b0e31c9e0eae68bbb0a358b4042ada9>2.4 - 公开地暴露你的应用</h1></div><div class=td-content><h1 id=pg-8ef4dad8f743b191a9e8c6f891cb191a>2.4.1 - 使用 Service 暴露你的应用</h1><div class=lead>了解 Kubernetes 中的 Service。 理解标签和选择算符如何关联到 Service。 在 Kubernetes 集群外暴露应用。</div><!doctype html><html lang=zh><body><div class=layout id=top><main class=content><div class=row><div class=col-md-8><h3>目标</h3><ul><li>了解 Kubernetes 中的 Service</li><li>了解标签(Label)和选择算符(Selector)如何与 Service 关联</li><li>在 Kubernetes 集群外用 Service 暴露应用</li></ul></div><div class=col-md-8><h3>Kubernetes Service 总览</h3><p>Kubernetes <a href=/zh-cn/docs/concepts/workloads/pods/>Pod</a> 是转瞬即逝的。 Pod 拥有 <a href=/zh-cn/docs/concepts/workloads/pods/pod-lifecycle/>生命周期</a>。 当一个工作节点挂掉后, 在节点上运行的 Pod 也会消亡。 <a href=/zh-cn/docs/concepts/workloads/controllers/replicaset/>ReplicaSet</a> 会自动地通过创建新的 Pod 驱动集群回到目标状态,以保证应用正常运行。 换一个例子,考虑一个具有 3 个副本的用作图像处理的后端程序。 这些副本是可替换的。前端系统不应该关心后端副本,即使某个 Pod 丢失或被重新创建。 此外,Kubernetes 集群中的每个 Pod 都有一个唯一的 IP 地址,即使是在同一个 Node 上的 Pod 也是如此, 因此需要一种方法来自动协调 Pod 之间的变化,以便应用保持运行。</p><p>Kubernetes 中的服务(Service)是一种抽象概念,它定义了 Pod 的逻辑集和访问 Pod 的协议。 Service 使从属 Pod 之间的松耦合成为可能。 和所有 Kubernetes 对象清单一样, Service 用 YAML 或者 JSON 来定义。 Service 下的一组 Pod 通常由一个 <i>标签选择算符</i> 来标记 (请参阅下面的说明为什么你可能想要一个 spec 中不包含 <code>selector</code> 的 Service)。</p><p>尽管每个 Pod 都有一个唯一的 IP 地址,但是如果没有 Service,这些 IP 不会被公开到集群外部。 Service 允许你的应用接收流量。 通过设置 Service 的 <code>spec</code> 中的 <code>type</code>,你可以用不同的方式公开 Service:</p><ul><li><i>ClusterIP</i>(默认)- 在集群的内部 IP 上公开 Service。这种类型使得 Service 只能从集群内访问。</li><li><i>NodePort</i> - 使用 NAT 在集群中每个选定 Node 的相同端口上公开 Service 。使用<code><NodeIP>:<NodePort></code> 从集群外部访问 Service。是 ClusterIP 的超集。</li><li><i>LoadBalancer</i> - 在当前云中创建一个外部负载均衡器(如果支持的话),并为 Service 分配一个固定的外部IP。是 NodePort 的超集。</li><li><i>ExternalName</i> - 将 Service 映射到 <code>externalName</code> 字段的内容(例如 <code>foo.bar.example.com</code>),通过返回带有该名称的 <code>CNAME</code> 记录实现。不设置任何类型的代理。这种类型需要 <code>kube-dns</code> 的 v1.7 或更高版本,或者 CoreDNS 的 0.8 或更高版本。</li></ul><p>关于不同 Service 类型的更多信息可以在<a href=/zh-cn/docs/tutorials/services/source-ip/>使用源 IP </a>教程找到。 也请参阅 <a href=/zh-cn/docs/tutorials/services/connect-applications-service/>使用 Service 连接到应用</a>。</p><p>另外,需要注意的是有一些 Service 的用例不需要在 spec 中定义 <code>selector</code>。 一个创建时未设置 <code>selector</code> 的 Service 也不会创建相应的 Endpoint 对象。 这允许用户手动将 Service 映射到特定的端点。 没有 selector 的另一种可能是你在严格使用 <code>type: ExternalName</code> 服务。</p></div><div class=col-md-4><div class="content__box content__box_lined"><h3>总结</h3><ul><li>将 Pod 暴露给外部通信</li><li>跨多个 Pod 的负载均衡</li><li>使用标签(Label)</li></ul></div><div class="content__box content__box_fill"><p><i>Kubernetes 的 Service 是一个抽象层,它定义了一组 Pod 的逻辑集,并为这些 Pod 支持外部流量暴露、负载平衡和服务发现。</i></p></div></div></div><br><div class=row><div class=col-md-8><h3>Service 和 Label</h3></div></div><div class=row><div class=col-md-8><p>Service 为一组 Pod 提供流量路由。Service 是一种抽象,允许 Kubernetes 中的 Pod 死亡和复制,而不会影响应用。 在依赖的 Pod(如应用中的前端和后端组件)之间进行发现和路由是由 Kubernetes Service 处理的。</p><p>Service 通过<a href=/zh-cn/docs/concepts/overview/working-with-objects/labels>标签和选择算符</a>来匹配一组 Pod, 它们是允许对 Kubernetes 中的对象进行逻辑操作的一种分组原语。 标签是附加在对象上的键/值对,可以以多种方式使用:</p><ul><li>指定用于开发、测试和生产的对象</li><li>嵌入版本标记</li><li>使用标记将对象分类</li></ul></div></div><br><div class=row><div class=col-md-8><p><img src=/docs/tutorials/kubernetes-basics/public/images/module_04_labels.svg></p></div></div><br><div class=row><div class=col-md-8><p>标签可以在对象创建时或之后附加到对象上。它们可以随时被修改。现在使用 Service 发布我们的应用并添加一些标签。</p></div></div><br><div class=row><div class=col-md-12><h3>第一步:创建新 Service</h3><p>让我们来验证我们的应用正在运行。我们将使用 <code>kubectl get</code> 命令并查找现有的 Pod:</p><p><code><b>kubectl get pods</b></code></p><p>如果没有 Pod 正在运行,则意味着之前教程中的对象已被清理。这时, 请返回并参考 <a href=/zh-cn/docs/tutorials/kubernetes-basics/deploy-app/deploy-intro#deploy-an-app>使用 kubectl 创建 Deployment</a> 教程重新创建 Deployment。 请等待几秒钟,然后再次列举 Pod。一旦看到一个 Pod 正在运行,你就可以继续了。</p><p>接下来,让我们列举当前集群中的 Service:</p><p><code><b>kubectl get services</b></code></p><p>我们有一个名为 <tt>kubernetes</tt> 的 Service ,它在 minikube 启动集群时默认创建。 要创建一个新的 Service 然后暴露给外部流量,我们将使用 <code>expose</code> 命令,并将 NodePort 作为参数。</p><p><code><b>kubectl expose deployment/kubernetes-bootcamp --type="NodePort" --port 8080</b></code></p><p>让我们再次运行 <code>get services</code> 子命令:</p><p><code><b>kubectl get services</b></code></p><p>我们现在有一个运行中的 Service 名为 kubernetes-bootcamp。 这里我们看到 Service 收到了一个唯一的集群内 IP(cluster-IP),一个内部端口和一个外部 IP (external-IP)(Node 的 IP)。</p><p>要得到外部打开的端口号(对于 <tt>type: NodePort</tt> 的服务),我们需要运行 <code>describe service</code> 子命令:</p><p><code><b>kubectl describe services/kubernetes-bootcamp</b></code></p><p>创建一个名为 <tt>NODE_PORT</tt> 的环境变量,它的值为所分配的 Node 端口:</p><p><code><b>export NODE_PORT="$(kubectl get services/kubernetes-bootcamp -o go-template='{{(index .spec.ports 0).nodePort}}')"</b></code><br><code><b>echo "NODE_PORT=$NODE_PORT"</b></code></p><p>现在我们可以使用 <code>curl</code>、Node 的 IP 地址和对外暴露的端口,来测试应用是否已经被公开到了集群外部:</p><p><code><b>curl http://"$(minikube ip):$NODE_PORT"</b></code></p><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>如果你正在使用 Docker Desktop 作为容器驱动来运行 minikube, 需要使用 minikube tunnel。 这是因为 Docker Desktop 内部的容器和宿主机是隔离的。<br><p>在另一个终端窗口中,执行:<br><code><b>minikube service kubernetes-bootcamp --url</b></code></p><p>输出结果如下:<pre><b>http://127.0.0.1:51082<br>! Because you are using a Docker driver on darwin, the terminal needs to be open to run it.</b></pre></p><p>然后使用提供的 URL 访问应用:<br><code><b>curl 127.0.0.1:51082</b></code></p></div><p>然后我们就会收到服务器的响应。Service 已经被暴露。</p></div></div><div class=row><div class=col-md-12><h3>第二步:使用标签</h3><div class=content><p>Deployment 自动给我们的 Pod 创建了一个标签。通过 <code>describe deployment</code> 子命令你可以看到那个标签的名称(对应 <em>key</em>):</p><p><code><b>kubectl describe deployment</b></code></p><p>让我们使用这个标签来查询 Pod 列表。我们将使用 <code>kubectl get pods</code> 命令和 <tt>-l</tt> 参数,后面给出标签值:</p><p><code><b>kubectl get pods -l app=kubernetes-bootcamp</b></code></p><p>你可以用同样的方法列出现有的 Service:</p><p><code><b>kubectl get services -l app=kubernetes-bootcamp</b></code></p><p>获取 Pod 的名称,然后存放到 <tt>POD_NAME</tt> 环境变量:</p><p><code><b>export POD_NAME="$(kubectl get pods -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}')"</b></code><br><code><b>echo "Name of the Pod: $POD_NAME"</b></code></p><p>要应用一个新的标签,我们使用 <code>label</code> 子命令,接着是对象类型、对象名称和新的标签:</p><p><code><b>kubectl label pods "$POD_NAME" version=v1</b></code></p><p>这将会在我们的 Pod 上应用一个新标签(我们把应用版本锁定到 Pod 上),然后我们可以通过 <code>describe pods</code> 命令检查它:</p><p><code><b>kubectl describe pods "$POD_NAME"</b></code></p><p>我们可以看到现在标签已经被附加到我们的 Pod 上。我们可以通过新的标签来查询 Pod 列表:</p><p><code><b>kubectl get pods -l version=v1</b></code></p><p>我们看到了对应的 Pod。</p></div></div><div class=row><div class=col-md-12><h3>第三步:删除一个 Service</h3><p>要删除一个 Service 你可以使用 <code>delete service</code> 子命令。这里也可以使用标签:</p><p><code><b>kubectl delete service -l app=kubernetes-bootcamp</b></code></p><p>确认对应的 Service 已经消失:</p><p><code><b>kubectl get services</b></code></p><p>这里确认了我们的 Service 已经被删除。要确认路由已经不再被公开,你可以 <tt>curl</tt> 之前公开的 IP 和端口:</p><p><code><b>curl http://"$(minikube ip):$NODE_PORT"</b></code></p><p>这证明了集群外部已经不再可以访问应用。 你可以通过在 Pod 内部运行 <tt>curl</tt> 确认应用仍在运行:</p><p><code><b>kubectl exec -ti $POD_NAME -- curl http://localhost:8080</b></code></p><p>这里我们看到应用是运行状态。这是因为 Deployment 正在管理应用。 要关闭应用,你还需要删除 Deployment。</p></div></div><div class=row><p>准备好之后,继续学习<a href=/zh-cn/docs/tutorials/kubernetes-basics/scale/scale-intro/ title=运行应用的多个实例>运行应用的多个实例</a>。</p></div></div></main></div></body></html></div><div class=td-content style=page-break-before:always><h1 id=pg-be4996c93fb39c459a30b6669569d423>2.5 - 扩缩你的应用</h1></div><div class=td-content><h1 id=pg-d1c15c9bd4f625adbc13149b1475287c>2.5.1 - 运行多实例的应用</h1><div class=lead>使用 kubectl 手动扩缩现有的应用</div><!doctype html><html lang=zh><body><div class=layout id=top><main class=content><div class=row><div class=col-md-8><h3>目标</h3><ul><li>用 kubectl 扩缩应用</li></ul></div><div class=col-md-8><h3>扩缩应用</h3><p>之前我们创建了一个 <a href=/zh-cn/docs/concepts/workloads/controllers/deployment/>Deployment</a>, 然后通过 <a href=/zh-cn/docs/concepts/services-networking/service/>Service</a> 让其可以公开访问。 Deployment 仅创建了一个 Pod 用于运行这个应用。当流量增加时,我们需要扩容应用满足用户需求。</p><p>如果你还没有学习过之前的章节, 从<a href=/zh-cn/docs/tutorials/kubernetes-basics/create-cluster/cluster-intro/>使用 Minikube 创建集群</a>开始。</p><p><b>扩缩</b>是通过改变 Deployment 中的副本数量来实现的。</p></div><div class=col-md-4><div class="content__box content__box_lined"><h3>小结:</h3><ul><li>扩缩一个 Deployment</li></ul></div><div class="content__box content__box_fill"><p><i>通过在使用 kubectl create deployment 命令时设置 --replicas 参数,你可以在启动 Deployment 时创建多个实例。</i></p></div></div></div><br><div class=row><div class=col-md-12><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>如果你是在<a href=/zh-cn/docs/tutorials/kubernetes-basics/expose/expose-intro/>上一节</a>之后尝试此操作, 那么你可能已经删除了你创建的服务或已创建了 <tt>type: NodePort</tt> 类别的 Service。 在本节中,假设为 kubernetes-bootcamp Deployment 创建了 <tt>type: LoadBalancer</tt> 类别的 Service。</p><p>如果你<b>没有</b>删除在<a href=/zh-cn/docs/tutorials/kubernetes-basics/expose/expose-intro>前一节</a>中创建的 Service, 请先删除该 Service,然后运行以下命令来创建一个新的 <tt>type</tt> 设置为 <tt>LoadBalancer</tt> 的 Service:</p><p><code><b>kubectl expose deployment/kubernetes-bootcamp --type="LoadBalancer" --port 8080</b></code></p></div></div></div><div class=row><div class=col-md-8><h2 style=color:#3771e3>扩缩概述</h2></div></div><div class=row><div class=col-md-1></div><div class=col-md-8><div id=myCarousel class=carousel data-ride=carousel data-interval=3000><ol class=carousel-indicators><li data-target=#myCarousel data-slide-to=0 class=active></li><li data-target=#myCarousel data-slide-to=1></li></ol><div class=carousel-inner role=listbox><div class="item carousel-item active"><img src=/docs/tutorials/kubernetes-basics/public/images/module_05_scaling1.svg></div><div class="item carousel-item"><img src=/docs/tutorials/kubernetes-basics/public/images/module_05_scaling2.svg></div></div><a class="left carousel-control" href=#myCarousel role=button data-slide=prev><span class=sr-only>Previous</span> </a><a class="right carousel-control" href=#myCarousel role=button data-slide=next><span class=sr-only>Next</span></a></div></div></div><br><div class=row><div class=col-md-8><p>对 Deployment 横向扩容将保证新的 Pod 被创建并调度到有可用资源的 Node 上,扩容会将 Pod 数量增加至新的预期状态。 Kubernetes 还支持 Pod 的<a href=/zh-cn/docs/tasks/run-application/horizontal-pod-autoscale/>自动扩缩容</a>, 但这并不在本教程的讨论范围内。 将 Pods 数量收缩到 0 也是可以的,这会终止指定 Deployment 上所有的 Pod。</p><p>运行多实例的应用,需要有方法在多个实例之间分配流量。Service 有一个集成的负载均衡器, 将网络流量分配到一个可公开访问的 Deployment 的所有 Pod 上。 服务将会一直通过端点来监视 Pod 的运行,保证流量只分配到可用的 Pod 上。</p></div><div class=col-md-4><div class="content__box content__box_fill"><p><i>扩缩是通过改变 Deployment 中的副本数量来实现的。</i></p></div></div></div><br><div class=row><div class=col-md-8><p>一旦有了多个应用实例,就可以进行滚动更新而无需停机。我们将会在教程的下一节介绍这些。 现在让我们进入终端,来扩缩我们的应用。</p></div></div><br><div class=row><div class=col-md-12><h3>扩容 Deployment</h3><p>要列出你的 Deployment,使用 <code>get deployments</code> 子命令:</p><p><code><b>kubectl get deployments</b></code></p><p>输出应该类似于:</p><pre> NAME READY UP-TO-DATE AVAILABLE AGE kubernetes-bootcamp 1/1 1 1 11m </pre><p>我们应该有 1 个 Pod。如果没有,请再次运行该命令。结果显示:</p><ul><li><em>NAME</em> 列出 Deployment 在集群中的名称。</li><li><em>READY</em> 显示当前/预期(CURRENT/DESIRED)副本数的比例。</li><li><em>UP-TO-DATE</em> 显示为了达到预期状态,而被更新的副本的数量。</li><li><em>AVAILABLE</em> 显示应用有多少个副本对你的用户可用。</li><li><em>AGE</em> 显示应用的运行时间。</li></ul><p>要查看由 Deployment 创建的 ReplicaSet,运行:</p><p><code><b>kubectl get rs</b></code></p><p>注意 ReplicaSet 名称总是遵循 <tt>[DEPLOYMENT-NAME]-[RANDOM-STRING]</tt> 的格式。 随机字符串是使用 <em>pod-template-hash</em> 作为种子随机生成的。</p><p>该输出有两个重要的列是:</p><ul><li><em>DESIRED</em> 显示了应用的预期副本数量,这是你在创建 Deployment 时定义的。这就是预期状态(desired state)。</li><li><em>CURRENT</em> 显示了当前正在运行的副本数量。</li></ul><p>接下来,让我们扩容 Deployment 到 4 个副本。 我们将使用 <code>kubectl scale</code> 命令,后面给出 Deployment 类型、名称和预期的实例数量:</p><p><code><b>kubectl scale deployments/kubernetes-bootcamp --replicas=4</b></code></p><p>要再次列举出你的 Deployment,使用 <code>get deployments</code>:</p><p><code><b>kubectl get deployments</b></code></p><p>更改已应用,我们有 4 个应用实例可用。接下来,让我们检查 Pod 的数量是否发生变化:</p><p><code><b>kubectl get pods -o wide</b></code></p><p>现在有 4 个 Pod,各有不同的 IP 地址。这一变化会记录到 Deployment 的事件日志中。 要检查这一点,请使用 <code>describe</code> 子命令:</p><p><code><b>kubectl describe deployments/kubernetes-bootcamp</b></code></p><p>你还可以从该命令的输出中看到,现在有 4 个副本。</p></div></div><div class=row><div class=col-md-12><h3>负载均衡</h3><p>让我们来检查 Service 是否在进行流量负载均衡。要查找对外公开的 IP 和端口, 我们可以使用在教程之前部份学到的 <code>describe services</code>:</p><p><code><b>kubectl describe services/kubernetes-bootcamp</b></code></p><p><code><b>export NODE_PORT="$(kubectl get services/kubernetes-bootcamp -o go-template='{{(index .spec.ports 0).nodePort}}')"</b></code><br><p><code><b>echo NODE_PORT=$NODE_PORT</b></code></p><p>接下来,我们将使用 <code>curl</code> 访问对外公开的 IP 和端口。多次执行以下命令:</p><p><code><b>curl http://"$(minikube ip):$NODE_PORT"</b></b></b></code></p><p>我们每个请求都命中了不同的 Pod,这证明负载均衡正在工作。</p><p>输出应该类似于:</p><pre> Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-644c5687f4-wp67j | v=1 Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-644c5687f4-hs9dj | v=1 Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-644c5687f4-4hjvf | v=1 Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-644c5687f4-wp67j | v=1 Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-644c5687f4-4hjvf | v=1 </pre><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>如果你使用 Docker Desktop 作为容器驱动程序运行 minikube,则需要 minikube 隧道。 这是因为 Docker Desktop 内的容器与主机隔离。<br><p>在单独的终端窗口中,执行:<br><code><b>minikube service kubernetes-bootcamp --url</b></code></p><p>输出看起来像这样:<pre><b>http://127.0.0.1:51082<br>! Because you are using a Docker driver on darwin, the terminal needs to be open to run it.</b></pre></p><p>然后使用给定的 URL 访问应用: <code><b>curl 127.0.0.1:51082</b></code></p></div></div></div><div class=row><div class=col-md-12><h3>缩容</h3><p>要将 Deployment 缩容到 2 个副本,请再次运行 <code>scale</code> 子命令:</p><p><code><b>kubectl scale deployments/kubernetes-bootcamp --replicas=2</b></code></p><p>要列举 Deployment 以检查是否应用了更改,使用 <code>get deployments</code> 子命令:</p><p><code><b>kubectl get deployments</b></code></p><p>副本数量减少到了 2 个,要列出 Pod 的数量,使用 <code>get pods</code>:</p><p><code><b>kubectl get pods -o wide</b></code></p><p>这证实了有 2 个 Pod 被终止。</p></div></div><div class=row><p>准备好之后,继续学习<a href=/zh-cn/docs/tutorials/kubernetes-basics/update/update-intro/ title="Performing a Rolling Update">执行滚动更新</a>。</p></div></main></div></body></html></div><div class=td-content style=page-break-before:always><h1 id=pg-62b8b17dadfb55f1801cf8439e944e58>2.6 - 更新你的应用</h1></div><div class=td-content><h1 id=pg-12e04355145afad615ca3c38335ba019>2.6.1 - 执行滚动更新</h1><div class=lead>使用 kubectl 执行滚动更新。</div><!doctype html><html lang=zh><body><div class=layout id=top><main class=content><div class=row><div class=col-md-8><h3>目标</h3><ul><li>使用 kubectl 执行滚动更新。</li></ul></div><div class=col-md-8><h3>更新应用程序</h3><p>用户希望应用程序始终可用,而开发人员则需要每天多次部署它们的新版本。 在 Kubernetes 中,这些是通过滚动更新(Rolling Updates)完成的。 <b>滚动更新</b> 允许通过使用新的实例逐步更新 Pod 实例,实现零停机的 Deployment 更新。 新的 Pod 将被调度到具有可用资源的节点上。</p><p>在前面的模块中,我们将应用程序扩展为运行多个实例。这是在不影响应用程序可用性的情况下执行更新的要求。 默认情况下,更新期间不可用的 pod 的最大值和可以创建的新 pod 数都是 1。这两个选项都可以配置为(pod)数字或百分比。 在 Kubernetes 中,更新是经过版本控制的,任何 Deployment 更新都可以恢复到以前的(稳定)版本。</p></div><div class=col-md-4><div class="content__box content__box_lined"><h3>摘要:</h3><ul><li>更新应用</li></ul></div><div class="content__box content__box_fill"><p><i>滚动更新通过逐步更新 Pod 实例并替换为新的实例,从而允许在 Deployment 更新过程中实现零停机。</i></p></div></div></div><br><div class=row><div class=col-md-8><h2 style=color:#3771e3>滚动更新概述</h2></div></div><div class=row><div class=col-md-1></div><div class=col-md-8><div id=myCarousel class=carousel data-ride=carousel data-interval=3000><ol class=carousel-indicators><li data-target=#myCarousel data-slide-to=0 class=active></li><li data-target=#myCarousel data-slide-to=1></li><li data-target=#myCarousel data-slide-to=2></li><li data-target=#myCarousel data-slide-to=3></li></ol><div class=carousel-inner role=listbox><div class="item carousel-item active"><img src=/docs/tutorials/kubernetes-basics/public/images/module_06_rollingupdates1.svg></div><div class="item carousel-item"><img src=/docs/tutorials/kubernetes-basics/public/images/module_06_rollingupdates2.svg></div><div class="item carousel-item"><img src=/docs/tutorials/kubernetes-basics/public/images/module_06_rollingupdates3.svg></div><div class="item carousel-item"><img src=/docs/tutorials/kubernetes-basics/public/images/module_06_rollingupdates4.svg></div></div><a class="left carousel-control" href=#myCarousel role=button data-slide=prev><span class=sr-only>Previous</span> </a><a class="right carousel-control" href=#myCarousel role=button data-slide=next><span class=sr-only>Next</span></a></div></div></div><br><div class=row><div class=col-md-8><p>与应用程序扩展类似,如果 Deployment 是公开的,Service 在更新期间仅将流量负载均衡到可用的 Pod。 可用的 Pod 是指应用程序对于用户可用的实例。</p><p>滚动更新允许以下操作:</p><ul><li>将应用程序从一个环境升级到另一个环境(通过容器镜像更新)</li><li>回滚到以前的版本</li><li>持续集成和持续交付应用程序,无需停机</li></ul></div><div class=col-md-4><div class="content__box content__box_fill"><p><i>如果 Deployment 是公开的,Service 在更新期间仅将流量负载均衡到可用的 Pod。</i></p></div></div></div><br><div class=row><div class=col-md-8><p>在下面的交互式教程中,我们将应用程序更新为新版本,并执行回滚。</p></div></div><br><div class=row><div class=col-md-12><h3>更新应用的版本</h3><p>要列举 Deployment,请运行 <code>get deployments</code> 子命令:</p><p><code><b>kubectl get deployments</b></code></p><p>要列举运行中的 Pod,请运行 <code>get pods</code> 子命令:</p><p><code><b>kubectl get pods</b></code></p><p>要查看应用程序当前的版本,请运行 <code>describe pods</code> 子命令, 然后查找 <code>Image</code> 字段:</p><p><code><b>kubectl describe pods</b></code></p><p>要更新应用程序的镜像版本到 v2,请使用 <code>set image</code> 子命令,后面给出 Deployment 名称和新的镜像版本:</p><p><code><b>kubectl set image deployments/kubernetes-bootcamp kubernetes-bootcamp=jocatalin/kubernetes-bootcamp:v2</b></code></p><p>此命令通知 Deployment 为应用程序使用不同的镜像,并启动滚动更新。 要检查新 Pod 的状态,并查看旧 Pod 的终止状况,请使用 <code>get pods</code> 子命令:</p><p><code><b>kubectl get pods</b></code></p></div></div><div class=row><div class=col-md-12><h3>第二步:验证更新</h3><p>首先,检查应用是否正在运行。要查找应用暴露的 IP 地址和端口,请运行 <code>describe services</code> 子命令:</p><p><code><b>kubectl describe services/kubernetes-bootcamp</b></code></p><p>创建名为 <tt>NODE_PORT</tt> 的环境变量,值为已被分配的 Node 端口:</p><p><code><b>export NODE_PORT="$(kubectl get services/kubernetes-bootcamp -o go-template='{{(index .spec.ports 0).nodePort}}')"</b></code><br><code><b>echo "NODE_PORT=$NODE_PORT"</b></code></p><p>接下来,针对所暴露的 IP 和端口执行 <code>curl</code>:</p><p><code><b>curl http://"$(minikube ip):$NODE_PORT"</b></code></p><p>你每次执行 <code>curl</code> 命令,都会命中不同的 Pod。注意现在所有的 Pod 都运行着最新版本(v2)。</p><p>你也可以通过运行 <code>rollout status</code> 来确认此次更新:</p><p><code><b>kubectl rollout status deployments/kubernetes-bootcamp</b></code></p><p>要查看应用程序当前的镜像版本,请运行 <code>describe pods</code> 子命令:</p><p><code><b>kubectl describe pods</b></code></p><p>在输出的 <code>Image</code> 字段,确认你正在运行最新的版本(v2)。</p></div></div><div class=row><div class=col-md-12><h3>回滚更新</h3><p>让我们执行另一次更新,并尝试部署一个标记为 <code>v10</code> 的镜像:</p><p><code><b>kubectl set image deployments/kubernetes-bootcamp kubernetes-bootcamp=gcr.io/google-samples/kubernetes-bootcamp:v10</b></code></p><p>使用 <code>get deployments</code> 来查看 Deployment 的状态:</p><p><code><b>kubectl get deployments</b></code></p><p>注意,这里的输出列出的可用 Pod 数量不符合预期。 运行 <code>get pods</code> 子命令来列举所有的 Pod:</p><p><code><b>kubectl get pods</b></code></p><p>注意此处部分 Pod 的状态是 <tt>ImagePullBackOff</tt>。</p><p>要深入了解此问题,请运行 <code>describe pods</code> 子命令:</p><p><code><b>kubectl describe pods</b></code></p><p>在受影响的 Pod 的 <code>Events</code> 部分, 可以注意到镜像的 <code>v10</code> 版本在仓库中不存在。</p><p>要回滚 Deployment 到上一个工作的版本,请使用 <code>rollout undo</code> 子命令:</p><p><code><b>kubectl rollout undo deployments/kubernetes-bootcamp</b></code></p><p><code>rollout undo</code> 命令会恢复 Deployment 到先前的已知状态(v2 的镜像)。 更新是受版本控制的,你可以恢复 Deployment 到任何先前已知状态。</p><p>使用 <code>get pods</code> 子命令再次列举 Pod:</p><p><code><b>kubectl get pods</b></code></p><p>有四个 Pod 正在运行。要检查这些 Pod 部署的镜像, 请使用 <code>describe pods</code> 子命令:</p><p><code><b>kubectl describe pods</b></code></p><p>Deployment 再次使用了稳定版本的应用程序(v2)。回滚成功。</p></div></div><div class=row><div class=col-md-12><p>记得清理本地集群:</p><p><code><b>kubectl delete deployments/kubernetes-bootcamp services/kubernetes-bootcamp</b></code></p></div></div></main></div></body></html></div><div class=td-content style=page-break-before:always><h1 id=pg-a3a0f1c6af19fc89ce24d8cd42c0249f>3 - 配置</h1></div><div class=td-content><h1 id=pg-b8268dd408000835b485de3a4f3343ab>3.1 - 通过 ConfigMap 更新配置</h1><p>本页提供了通过 ConfigMap 更新 Pod 中配置信息的分步示例, 本教程的前置任务是<a href=/zh-cn/docs/tasks/configure-pod-container/configure-pod-configmap/>配置 Pod 以使用 ConfigMap</a>。<br>在本教程结束时,你将了解如何变更运行中应用的配置。 本教程以 <code>alpine</code> 和 <code>nginx</code> 镜像为例。</p><h2 id=准备开始>准备开始</h2><p>你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 <a href=https://minikube.sigs.k8s.io/docs/tutorials/multi_node/>Minikube</a> 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:</p><ul><li><a href=https://killercoda.com/playgrounds/scenario/kubernetes>Killercoda</a></li><li><a href=https://labs.play-with-k8s.com/>玩转 Kubernetes</a></li></ul><p>你需要有 <a href=https://curl.se/>curl</a> 命令行工具,用于从终端或命令行界面发出 HTTP 请求。 如果你没有 <code>curl</code>,可以安装此工具。请查阅你本地操作系统的文档。</p><h2 id=教程目标>教程目标</h2><ul><li>通过作为卷挂载的 ConfigMap 更新配置</li><li>通过 ConfigMap 更新 Pod 的环境变量</li><li>在多容器 Pod 中通过 ConfigMap 更新配置</li><li>在包含边车容器的 Pod 中通过 ConfigMap 更新配置</li></ul><h2 id=rollout-configmap-volume>通过作为卷挂载的 ConfigMap 更新配置</h2><p>使用 <code>kubectl create configmap</code> 命令基于<a href=/zh-cn/docs/tasks/configure-pod-container/configure-pod-configmap/#create-configmaps-from-literal-values>字面值</a>创建一个 ConfigMap:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl create configmap sport --from-literal<span style=color:#666>=</span><span style=color:#b8860b>sport</span><span style=color:#666>=</span>football </span></span></code></pre></div><p>下面是一个 Deployment 清单示例,其中 ConfigMap <code>sport</code> 作为<a class=glossary-tooltip title='包含可被 Pod 中容器访问的数据的目录。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/storage/volumes/ target=_blank aria-label=卷>卷</a>挂载到 Pod 的唯一容器中。</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/deployments/deployment-with-configmap-as-volume.yaml download=deployments/deployment-with-configmap-as-volume.yaml><code>deployments/deployment-with-configmap-as-volume.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("deployments-deployment-with-configmap-as-volume-yaml")' title="复制 deployments/deployment-with-configmap-as-volume.yaml 到剪贴板"></img></div><div class=includecode id=deployments-deployment-with-configmap-as-volume-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>apps/v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Deployment<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>configmap-volume<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app.kubernetes.io/name</span>:<span style=color:#bbb> </span>configmap-volume<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>replicas</span>:<span style=color:#bbb> </span><span style=color:#666>3</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>matchLabels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app.kubernetes.io/name</span>:<span style=color:#bbb> </span>configmap-volume<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>template</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app.kubernetes.io/name</span>:<span style=color:#bbb> </span>configmap-volume<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>alpine<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>alpine:3<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>command</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- /bin/sh<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- -c<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- while true; do echo "$(date) My preferred sport is $(cat /etc/config/sport)";<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>sleep 10; done;<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>containerPort</span>:<span style=color:#bbb> </span><span style=color:#666>80</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumeMounts</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>config-volume<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>mountPath</span>:<span style=color:#bbb> </span>/etc/config<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumes</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>config-volume<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>configMap</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>sport</span></span></code></pre></div></div></div><p>创建此 Deployment:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/deployments/deployment-with-configmap-as-volume.yaml </span></span></code></pre></div><p>检查此 Deployment 的 Pod 以确保它们已就绪(通过<a class=glossary-tooltip title=选择算符允许用户通过标签对一组资源对象进行筛选过滤。 data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/overview/working-with-objects/labels/ target=_blank aria-label=选择算符>选择算符</a>进行匹配):</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods --selector<span style=color:#666>=</span>app.kubernetes.io/name<span style=color:#666>=</span>configmap-volume </span></span></code></pre></div><p>你应该会看到类似以下的输出:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE configmap-volume-6b976dfdcf-qxvbm 1/1 Running 0 72s configmap-volume-6b976dfdcf-skpvm 1/1 Running 0 72s configmap-volume-6b976dfdcf-tbc6r 1/1 Running 0 72s </code></pre><p>在运行这些 Pod 之一的每个节点上,kubelet 获取该 ConfigMap 的数据,并将其转换为本地卷中的文件。 然后,kubelet 按照 Pod 模板中指定的方式将该卷挂载到容器中。 在该容器中运行的代码从文件中加载信息,并使用它将报告打印到标准输出。 你可以通过查看该 Deployment 中其中一个 Pod 的日志来检查此报告:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 选择一个属于该 Deployment 的 Pod,并查看其日志</span> </span></span><span style=display:flex><span>kubectl logs deployments/configmap-volume </span></span></code></pre></div><p>你应该会看到类似以下的输出:</p><pre tabindex=0><code>Found 3 pods, using pod/configmap-volume-76d9c5678f-x5rgj Thu Jan 4 14:06:46 UTC 2024 My preferred sport is football Thu Jan 4 14:06:56 UTC 2024 My preferred sport is football Thu Jan 4 14:07:06 UTC 2024 My preferred sport is football Thu Jan 4 14:07:16 UTC 2024 My preferred sport is football Thu Jan 4 14:07:26 UTC 2024 My preferred sport is football </code></pre><p>编辑 ConfigMap:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl edit configmap sport </span></span></code></pre></div><p>在出现的编辑器中,将键 <code>sport</code> 的值从 <code>football</code> 变更为 <code>cricket</code>。 保存你的变更。kubectl 工具会相应地更新 ConfigMap(如果报错,请重试)。</p><p>以下是你编辑后该清单可能的样子:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>data</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>sport</span>:<span style=color:#bbb> </span>cricket<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>ConfigMap<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:#080;font-style:italic># 你可以保留现有的 metadata 不变。</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:#080;font-style:italic># 你将看到的值与本例的值不会完全一样。</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>creationTimestamp</span>:<span style=color:#bbb> </span><span style=color:#b44>"2024-01-04T14:05:06Z"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>sport<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>namespace</span>:<span style=color:#bbb> </span>default<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>resourceVersion</span>:<span style=color:#bbb> </span><span style=color:#b44>"1743935"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>uid</span>:<span style=color:#bbb> </span>024ee001-fe72-487e-872e-34d6464a8a23<span style=color:#bbb> </span></span></span></code></pre></div><p>你应该会看到以下输出:</p><pre tabindex=0><code>configmap/sport edited </code></pre><p>查看属于此 Deployment 的 Pod 之一的日志(并跟踪最新写入的条目):</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl logs deployments/configmap-volume --follow </span></span></code></pre></div><p>几秒钟后,你应该会看到日志输出中的如下变化:</p><pre tabindex=0><code>Thu Jan 4 14:11:36 UTC 2024 My preferred sport is football Thu Jan 4 14:11:46 UTC 2024 My preferred sport is football Thu Jan 4 14:11:56 UTC 2024 My preferred sport is football Thu Jan 4 14:12:06 UTC 2024 My preferred sport is cricket Thu Jan 4 14:12:16 UTC 2024 My preferred sport is cricket </code></pre><p>当你有一个 ConfigMap 通过 <code>configMap</code> 卷或 <code>projected</code> 卷映射到运行中的 Pod, 并且你更新了该 ConfigMap 时,运行中的 Pod 几乎会立即更新。<br>但是,你的应用只有在编写为轮询变更或监视文件更新时才能看到变更。<br>启动时一次性加载其配置的应用将不会注意到变更。</p><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>从更新 ConfigMap 的那一刻到将新的键投射到 Pod 的那一刻,整个延迟可能与 kubelet 同步周期相同。 另请参阅<a href=/zh-cn/docs/tasks/configure-pod-container/configure-pod-configmap/#mounted-configmaps-are-updated-automatically>挂载的 ConfigMap 会被自动更新</a>。</p></div><h2 id=rollout-configmap-env>通过 ConfigMap 更新 Pod 的环境变量</h2><p>使用 <code>kubectl create configmap</code> 命令基于<a href=/zh-cn/docs/tasks/configure-pod-container/configure-pod-configmap/#create-configmaps-from-literal-values>字面值</a>创建一个 ConfigMap:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl create configmap fruits --from-literal<span style=color:#666>=</span><span style=color:#b8860b>fruits</span><span style=color:#666>=</span>apples </span></span></code></pre></div><p>下面是一个 Deployment 清单的示例,包含一个通过 ConfigMap <code>fruits</code> 配置的环境变量。</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/deployments/deployment-with-configmap-as-envvar.yaml download=deployments/deployment-with-configmap-as-envvar.yaml><code>deployments/deployment-with-configmap-as-envvar.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("deployments-deployment-with-configmap-as-envvar-yaml")' title="复制 deployments/deployment-with-configmap-as-envvar.yaml 到剪贴板"></img></div><div class=includecode id=deployments-deployment-with-configmap-as-envvar-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>apps/v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Deployment<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>configmap-env-var<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app.kubernetes.io/name</span>:<span style=color:#bbb> </span>configmap-env-var<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>replicas</span>:<span style=color:#bbb> </span><span style=color:#666>3</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>matchLabels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app.kubernetes.io/name</span>:<span style=color:#bbb> </span>configmap-env-var<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>template</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app.kubernetes.io/name</span>:<span style=color:#bbb> </span>configmap-env-var<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>alpine<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>alpine:3<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>env</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>FRUITS<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>valueFrom</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>configMapKeyRef</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>key</span>:<span style=color:#bbb> </span>fruits<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>fruits<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>command</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- /bin/sh<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- -c<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- while true; do echo "$(date) The basket is full of $FRUITS";<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>sleep 10; done;<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>containerPort</span>:<span style=color:#bbb> </span><span style=color:#666>80</span></span></span></code></pre></div></div></div><p>创建此 Deployment:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/deployments/deployment-with-configmap-as-envvar.yaml </span></span></code></pre></div><p>检查此 Deployment 的 Pod 以确保它们已就绪(通过<a class=glossary-tooltip title=选择算符允许用户通过标签对一组资源对象进行筛选过滤。 data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/overview/working-with-objects/labels/ target=_blank aria-label=选择算符>选择算符</a>进行匹配):</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods --selector<span style=color:#666>=</span>app.kubernetes.io/name<span style=color:#666>=</span>configmap-env-var </span></span></code></pre></div><p>你应该会看到类似以下的输出:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE configmap-env-var-59cfc64f7d-74d7z 1/1 Running 0 46s configmap-env-var-59cfc64f7d-c4wmj 1/1 Running 0 46s configmap-env-var-59cfc64f7d-dpr98 1/1 Running 0 46s </code></pre><p>ConfigMap 中的键值对被配置为 Pod 容器中的环境变量。 通过查看属于该 Deployment 的某个 Pod 的日志来检查这一点。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl logs deployment/configmap-env-var </span></span></code></pre></div><p>你应该会看到类似以下的输出:</p><pre tabindex=0><code>Found 3 pods, using pod/configmap-env-var-7c994f7769-l74nq Thu Jan 4 16:07:06 UTC 2024 The basket is full of apples Thu Jan 4 16:07:16 UTC 2024 The basket is full of apples Thu Jan 4 16:07:26 UTC 2024 The basket is full of apples </code></pre><p>编辑 ConfigMap:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl edit configmap fruits </span></span></code></pre></div><p>在出现的编辑器中,将键 <code>fruits</code> 的值从 <code>apples</code> 变更为 <code>mangoes</code>。 保存你的变更。kubectl 工具会相应地更新 ConfigMap(如果报错,请重试)。</p><p>以下是你编辑后该清单可能的样子:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>data</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>fruits</span>:<span style=color:#bbb> </span>mangoes<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>ConfigMap<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:#080;font-style:italic># 你可以保留现有的 metadata 不变。</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:#080;font-style:italic># 你将看到的值与本例的值不会完全一样。</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>creationTimestamp</span>:<span style=color:#bbb> </span><span style=color:#b44>"2024-01-04T16:04:19Z"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>fruits<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>namespace</span>:<span style=color:#bbb> </span>default<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>resourceVersion</span>:<span style=color:#bbb> </span><span style=color:#b44>"1749472"</span><span style=color:#bbb> </span></span></span></code></pre></div><p>你应该看到以下输出:</p><pre tabindex=0><code>configmap/fruits edited </code></pre><p>查看此 Deployment 的日志,并观察几秒钟的输出:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 如上所述,输出不会有变化</span> </span></span><span style=display:flex><span>kubectl logs deployments/configmap-env-var --follow </span></span></code></pre></div><p>请注意,即使你编辑了 ConfigMap,输出仍然<strong>没有变化</strong>:</p><pre tabindex=0><code>Thu Jan 4 16:12:56 UTC 2024 The basket is full of apples Thu Jan 4 16:13:06 UTC 2024 The basket is full of apples Thu Jan 4 16:13:16 UTC 2024 The basket is full of apples Thu Jan 4 16:13:26 UTC 2024 The basket is full of apples </code></pre><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>尽管 ConfigMap 中的键的取值已经变更,Pod 中的环境变量仍然显示先前的值。 这是因为当源数据变更时,在 Pod 内运行的进程的环境变量<strong>不会</strong>被更新; 如果你想强制更新,需要让 Kubernetes 替换现有的 Pod。新 Pod 将使用更新的信息来运行。</p></div><p>你可以触发该替换。使用 <a href=/zh-cn/docs/reference/kubectl/generated/kubectl_rollout/><code>kubectl rollout</code></a> 为 Deployment 执行上线操作:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 触发上线操作</span> </span></span><span style=display:flex><span>kubectl rollout restart deployment configmap-env-var </span></span><span style=display:flex><span> </span></span><span style=display:flex><span><span style=color:#080;font-style:italic># 等待上线操作完成</span> </span></span><span style=display:flex><span>kubectl rollout status deployment configmap-env-var --watch<span style=color:#666>=</span><span style=color:#a2f>true</span> </span></span></code></pre></div><p>接下来,检查 Deployment:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get deployment configmap-env-var </span></span></code></pre></div><p>你应该会看到类似以下的输出:</p><pre tabindex=0><code>NAME READY UP-TO-DATE AVAILABLE AGE configmap-env-var 3/3 3 3 12m </code></pre><p>检查 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods --selector<span style=color:#666>=</span>app.kubernetes.io/name<span style=color:#666>=</span>configmap-env-var </span></span></code></pre></div><p>上线操作会导致 Kubernetes 为 Deployment 新建一个 <a class=glossary-tooltip title='ReplicaSet 是下一代副本控制器。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/workloads/controllers/replicaset/ target=_blank aria-label=ReplicaSet>ReplicaSet</a>; 这意味着现有的 Pod 最终会终止,并创建新的 Pod。几秒钟后,你应该会看到类似以下的输出:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE configmap-env-var-6d94d89bf5-2ph2l 1/1 Running 0 13s configmap-env-var-6d94d89bf5-74twx 1/1 Running 0 8s configmap-env-var-6d94d89bf5-d5vx8 1/1 Running 0 11s </code></pre><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>请等待旧的 Pod 完全终止后再进行下一步。</p></div><p>查看此 Deployment 中某个 Pod 的日志:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 选择属于 Deployment 的一个 Pod,并查看其日志</span> </span></span><span style=display:flex><span>kubectl logs deployment/configmap-env-var </span></span></code></pre></div><p>你应该会看到类似以下的输出:</p><pre tabindex=0><code>Found 3 pods, using pod/configmap-env-var-6d9ff89fb6-bzcf6 Thu Jan 4 16:30:35 UTC 2024 The basket is full of mangoes Thu Jan 4 16:30:45 UTC 2024 The basket is full of mangoes Thu Jan 4 16:30:55 UTC 2024 The basket is full of mangoes </code></pre><p>这个场景演示了在 Pod 中如何更新从 ConfigMap 派生的环境变量。ConfigMap 值的变更在随后的上线操作期间被应用到 Pod。如果 Pod 由于其他原因(例如 Deployment 扩容)被创建, 那么新 Pod 也会使用最新的配置值; 如果你不触发上线操作,你可能会发现你的应用在运行过程中混用了新旧环境变量值。</p><h2 id=rollout-configmap-multiple-containers>在多容器 Pod 中通过 ConfigMap 更新配置</h2><p>使用 <code>kubectl create configmap</code> 命令基于<a href=/zh-cn/docs/tasks/configure-pod-container/configure-pod-configmap/#create-configmaps-from-literal-values>字面值</a>创建一个 ConfigMap:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl create configmap color --from-literal<span style=color:#666>=</span><span style=color:#b8860b>color</span><span style=color:#666>=</span>red </span></span></code></pre></div><p>下面是一个 Deployment 清单的示例,该 Deployment 管理一组 Pod,每个 Pod 有两个容器。 这两个容器共享一个 <code>emptyDir</code> 卷并使用此卷进行通信。第一个容器运行 Web 服务器(<code>nginx</code>)。 在 Web 服务器容器中共享卷的挂载路径是 <code>/usr/share/nginx/html</code>。 第二个辅助容器基于 <code>alpine</code>,对于这个容器,<code>emptyDir</code> 卷被挂载在 <code>/pod-data</code>。 辅助容器生成一个 HTML 文件,其内容基于 ConfigMap。Web 服务器容器通过 HTTP 提供此 HTML 文件。</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/deployments/deployment-with-configmap-two-containers.yaml download=deployments/deployment-with-configmap-two-containers.yaml><code>deployments/deployment-with-configmap-two-containers.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("deployments-deployment-with-configmap-two-containers-yaml")' title="复制 deployments/deployment-with-configmap-two-containers.yaml 到剪贴板"></img></div><div class=includecode id=deployments-deployment-with-configmap-two-containers-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>apps/v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Deployment<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>configmap-two-containers<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app.kubernetes.io/name</span>:<span style=color:#bbb> </span>configmap-two-containers<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>replicas</span>:<span style=color:#bbb> </span><span style=color:#666>3</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>matchLabels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app.kubernetes.io/name</span>:<span style=color:#bbb> </span>configmap-two-containers<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>template</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app.kubernetes.io/name</span>:<span style=color:#bbb> </span>configmap-two-containers<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumes</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>shared-data<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>emptyDir</span>:<span style=color:#bbb> </span>{}<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>config-volume<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>configMap</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>color<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumeMounts</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>shared-data<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>mountPath</span>:<span style=color:#bbb> </span>/usr/share/nginx/html<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>alpine<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>alpine:3<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumeMounts</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>shared-data<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>mountPath</span>:<span style=color:#bbb> </span>/pod-data<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>config-volume<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>mountPath</span>:<span style=color:#bbb> </span>/etc/config<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>command</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- /bin/sh<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- -c<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- while true; do echo "$(date) My preferred color is $(cat /etc/config/color)" > /pod-data/index.html;<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>sleep 10; done;<span style=color:#bbb> </span></span></span></code></pre></div></div></div><p>创建此 Deployment:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/deployments/deployment-with-configmap-two-containers.yaml </span></span></code></pre></div><p>检查此 Deployment 的 Pod 以确保它们已就绪(通过<a class=glossary-tooltip title=选择算符允许用户通过标签对一组资源对象进行筛选过滤。 data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/overview/working-with-objects/labels/ target=_blank aria-label=选择算符>选择算符</a>进行匹配):</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods --selector<span style=color:#666>=</span>app.kubernetes.io/name<span style=color:#666>=</span>configmap-two-containers </span></span></code></pre></div><p>你应该会看到类似以下的输出:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE configmap-two-containers-565fb6d4f4-2xhxf 2/2 Running 0 20s configmap-two-containers-565fb6d4f4-g5v4j 2/2 Running 0 20s configmap-two-containers-565fb6d4f4-mzsmf 2/2 Running 0 20s </code></pre><p>公开 Deployment(<code>kubectl</code> 工具会为你创建 <a class=glossary-tooltip title='将运行在一组 Pods 上的应用程序公开为网络服务的抽象方法。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/services-networking/service/ target=_blank aria-label=Service>Service</a>):</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl expose deployment configmap-two-containers --name<span style=color:#666>=</span>configmap-service --port<span style=color:#666>=</span><span style=color:#666>8080</span> --target-port<span style=color:#666>=</span><span style=color:#666>80</span> </span></span></code></pre></div><p>使用 <code>kubectl</code> 转发端口:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 此命令将在后台运行</span> </span></span><span style=display:flex><span>kubectl port-forward service/configmap-service 8080:8080 & </span></span></code></pre></div><p>访问服务:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>curl http://localhost:8080 </span></span></code></pre></div><p>你应该会看到类似以下的输出:</p><pre tabindex=0><code>Fri Jan 5 08:08:22 UTC 2024 My preferred color is red </code></pre><p>编辑 ConfigMap:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl edit configmap color </span></span></code></pre></div><p>在出现的编辑器中,将键 <code>color</code> 的值从 <code>red</code> 变更为 <code>blue</code>。 保存你的变更。kubectl 工具会相应地更新 ConfigMap(如果报错,请重试)。</p><p>以下是你编辑后该清单可能的样子:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>data</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>color</span>:<span style=color:#bbb> </span>blue<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>ConfigMap<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:#080;font-style:italic># 你可以保留现有的 metadata 不变。</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:#080;font-style:italic># 你将看到的值与本例的值不会完全一样。</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>creationTimestamp</span>:<span style=color:#bbb> </span><span style=color:#b44>"2024-01-05T08:12:05Z"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>color<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>namespace</span>:<span style=color:#bbb> </span>configmap<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>resourceVersion</span>:<span style=color:#bbb> </span><span style=color:#b44>"1801272"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>uid</span>:<span style=color:#bbb> </span>80d33e4a-cbb4-4bc9-ba8c-544c68e425d6<span style=color:#bbb> </span></span></span></code></pre></div><p>循环访问服务 URL 几秒钟。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 当你满意时可以取消此操作 (Ctrl-C)</span> </span></span><span style=display:flex><span><span style=color:#a2f;font-weight:700>while</span> true; <span style=color:#a2f;font-weight:700>do</span> curl --connect-timeout 7.5 http://localhost:8080; sleep 10; <span style=color:#a2f;font-weight:700>done</span> </span></span></code></pre></div><p>你应该会看到如下的输出变化:</p><pre tabindex=0><code>Fri Jan 5 08:14:00 UTC 2024 My preferred color is red Fri Jan 5 08:14:02 UTC 2024 My preferred color is red Fri Jan 5 08:14:20 UTC 2024 My preferred color is red Fri Jan 5 08:14:22 UTC 2024 My preferred color is red Fri Jan 5 08:14:32 UTC 2024 My preferred color is blue Fri Jan 5 08:14:43 UTC 2024 My preferred color is blue Fri Jan 5 08:15:00 UTC 2024 My preferred color is blue </code></pre><h2 id=rollout-configmap-sidecar>在包含边车容器的 Pod 中通过 ConfigMap 更新配置</h2><p>要重现上述场景,可以使用<a href=/zh-cn/docs/concepts/workloads/pods/sidecar-containers/>边车容器</a>作为辅助容器来写入 HTML 文件。<br>由于边车容器在概念上是一个 Init 容器,因此保证会在主要 Web 服务器容器启动之前启动。<br>这确保了当 Web 服务器准备好提供服务时,HTML 文件始终可用。<br>请参阅<a href=/zh-cn/docs/concepts/workloads/pods/sidecar-containers/#enabling-sidecar-containers>启用边车容器</a>以使用此特性。</p><p>如果你从前一个场景继续操作,你可以在此场景中重用名为 <code>color</code> 的 ConfigMap。<br>如果你是独立执行此场景,请使用 <code>kubectl create configmap</code> 命令基于<a href=/zh-cn/docs/tasks/configure-pod-container/configure-pod-configmap/#create-configmaps-from-literal-values>字面值</a>创建一个 ConfigMap:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl create configmap color --from-literal<span style=color:#666>=</span><span style=color:#b8860b>color</span><span style=color:#666>=</span>blue </span></span></code></pre></div><p>以下是一个 Deployment 清单示例,该 Deployment 管理一组 Pod,每个 Pod 有一个主容器和一个边车容器。 这两个容器共享一个 <code>emptyDir</code> 卷并使用此卷来通信。主容器运行 Web 服务器(NGINX)。 在 Web 服务器容器中共享卷的挂载路径是 <code>/usr/share/nginx/html</code>。 第二个容器是基于 Alpine Linux 作为辅助容器的边车容器。对于这个辅助容器,<code>emptyDir</code> 卷被挂载在 <code>/pod-data</code>。 边车容器写入一个 HTML 文件,其内容基于 ConfigMap。Web 服务器容器通过 HTTP 提供此 HTML 文件。</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/deployments/deployment-with-configmap-and-sidecar-container.yaml download=deployments/deployment-with-configmap-and-sidecar-container.yaml><code>deployments/deployment-with-configmap-and-sidecar-container.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("deployments-deployment-with-configmap-and-sidecar-container-yaml")' title="复制 deployments/deployment-with-configmap-and-sidecar-container.yaml 到剪贴板"></img></div><div class=includecode id=deployments-deployment-with-configmap-and-sidecar-container-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>apps/v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Deployment<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>configmap-sidecar-container<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app.kubernetes.io/name</span>:<span style=color:#bbb> </span>configmap-sidecar-container<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>replicas</span>:<span style=color:#bbb> </span><span style=color:#666>3</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>matchLabels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app.kubernetes.io/name</span>:<span style=color:#bbb> </span>configmap-sidecar-container<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>template</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app.kubernetes.io/name</span>:<span style=color:#bbb> </span>configmap-sidecar-container<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumes</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>shared-data<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>emptyDir</span>:<span style=color:#bbb> </span>{}<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>config-volume<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>configMap</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>color<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumeMounts</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>shared-data<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>mountPath</span>:<span style=color:#bbb> </span>/usr/share/nginx/html<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>initContainers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>alpine<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>alpine:3<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>restartPolicy</span>:<span style=color:#bbb> </span>Always<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumeMounts</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>shared-data<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>mountPath</span>:<span style=color:#bbb> </span>/pod-data<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>config-volume<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>mountPath</span>:<span style=color:#bbb> </span>/etc/config<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>command</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- /bin/sh<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- -c<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- while true; do echo "$(date) My preferred color is $(cat /etc/config/color)" > /pod-data/index.html;<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>sleep 10; done;<span style=color:#bbb> </span></span></span></code></pre></div></div></div><p>创建此 Deployment:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/deployments/deployment-with-configmap-and-sidecar-container.yaml </span></span></code></pre></div><p>检查此 Deployment 的 Pod 以确保它们已就绪(通过<a class=glossary-tooltip title=选择算符允许用户通过标签对一组资源对象进行筛选过滤。 data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/overview/working-with-objects/labels/ target=_blank aria-label=选择算符>选择算符</a>进行匹配):</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods --selector<span style=color:#666>=</span>app.kubernetes.io/name<span style=color:#666>=</span>configmap-sidecar-container </span></span></code></pre></div><p>你应该会看到类似以下的输出:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE configmap-sidecar-container-5fb59f558b-87rp7 2/2 Running 0 94s configmap-sidecar-container-5fb59f558b-ccs7s 2/2 Running 0 94s configmap-sidecar-container-5fb59f558b-wnmgk 2/2 Running 0 94s </code></pre><p>公开 Deployment(<code>kubectl</code> 工具会为你创建一个 <a class=glossary-tooltip title='将运行在一组 Pods 上的应用程序公开为网络服务的抽象方法。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/services-networking/service/ target=_blank aria-label=Service>Service</a>):</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl expose deployment configmap-sidecar-container --name<span style=color:#666>=</span>configmap-sidecar-service --port<span style=color:#666>=</span><span style=color:#666>8081</span> --target-port<span style=color:#666>=</span><span style=color:#666>80</span> </span></span></code></pre></div><p>使用 <code>kubectl</code> 转发端口:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 此命令将在后台运行</span> </span></span><span style=display:flex><span>kubectl port-forward service/configmap-sidecar-service 8081:80 & </span></span></code></pre></div><p>访问服务:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>curl http://localhost:8081 </span></span></code></pre></div><p>你应该看到类似以下的输出:</p><pre tabindex=0><code>Sat Feb 17 13:09:05 UTC 2024 My preferred color is blue </code></pre><p>编辑 ConfigMap:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl edit configmap color </span></span></code></pre></div><p>在出现的编辑器中,将键 <code>color</code> 的值从 <code>blue</code> 变更为 <code>green</code>。 保存你的变更。kubectl 工具会相应地更新 ConfigMap(如果报错,请重试)。</p><p>以下是你编辑后该清单可能的样子:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>data</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>color</span>:<span style=color:#bbb> </span>green<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>ConfigMap<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:#080;font-style:italic># 你可以保留现有的 metadata 不变。</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:#080;font-style:italic># 你将看到的值与本例的值不会完全一样。</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>creationTimestamp</span>:<span style=color:#bbb> </span><span style=color:#b44>"2024-02-17T12:20:30Z"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>color<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>namespace</span>:<span style=color:#bbb> </span>default<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>resourceVersion</span>:<span style=color:#bbb> </span><span style=color:#b44>"1054"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>uid</span>:<span style=color:#bbb> </span>e40bb34c-58df-4280-8bea-6ed16edccfaa<span style=color:#bbb> </span></span></span></code></pre></div><p>循环访问服务 URL 几秒钟。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 当你满意时可以取消此操作 (Ctrl-C)</span> </span></span><span style=display:flex><span><span style=color:#a2f;font-weight:700>while</span> true; <span style=color:#a2f;font-weight:700>do</span> curl --connect-timeout 7.5 http://localhost:8081; sleep 10; <span style=color:#a2f;font-weight:700>done</span> </span></span></code></pre></div><p>你应该会看到如下的输出变化:</p><pre tabindex=0><code>Sat Feb 17 13:12:35 UTC 2024 My preferred color is blue Sat Feb 17 13:12:45 UTC 2024 My preferred color is blue Sat Feb 17 13:12:55 UTC 2024 My preferred color is blue Sat Feb 17 13:13:05 UTC 2024 My preferred color is blue Sat Feb 17 13:13:15 UTC 2024 My preferred color is green Sat Feb 17 13:13:25 UTC 2024 My preferred color is green Sat Feb 17 13:13:35 UTC 2024 My preferred color is green </code></pre><h2 id=rollout-configmap-immutable-volume>通过作为卷挂载的不可变 ConfigMap 更新配置</h2><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>不可变 ConfigMap 专门用于恒定且预期<strong>不会</strong>随时间变化的配置。 将 ConfigMap 标记为不可变可以提高性能,因为 kubelet 不会监视变更。</p><p>如果你确实需要进行变更,你应计划:</p><ul><li>变更 ConfigMap 的名称,并转而运行引用新名称的 Pod</li><li>替换集群中之前运行使用旧值的 Pod 的所有节点</li><li>在任何之前加载过旧 ConfigMap 的节点上重新启动 kubelet</li></ul></div><p>以下是一个<a href=/zh-cn/docs/concepts/configuration/configmap/#configmap-immutable>不可变 ConfigMap</a>的示例清单。</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/configmap/immutable-configmap.yaml download=configmap/immutable-configmap.yaml><code>configmap/immutable-configmap.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("configmap-immutable-configmap-yaml")' title="复制 configmap/immutable-configmap.yaml 到剪贴板"></img></div><div class=includecode id=configmap-immutable-configmap-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>data</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>company_name</span>:<span style=color:#bbb> </span><span style=color:#b44>"ACME, Inc."</span><span style=color:#bbb> </span><span style=color:#080;font-style:italic># 虚构的公司名称</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>ConfigMap<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>immutable</span>:<span style=color:#bbb> </span><span style=color:#a2f;font-weight:700>true</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>company-name-20150801</span></span></code></pre></div></div></div><p>创建不可变 ConfigMap:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/configmap/immutable-configmap.yaml </span></span></code></pre></div><p>下面是一个 Deployment 清单示例,其中不可变 ConfigMap <code>company-name-20150801</code> 作为<a class=glossary-tooltip title='包含可被 Pod 中容器访问的数据的目录。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/storage/volumes/ target=_blank aria-label=卷>卷</a>挂载到 Pod 的唯一容器中。</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/deployments/deployment-with-immutable-configmap-as-volume.yaml download=deployments/deployment-with-immutable-configmap-as-volume.yaml><code>deployments/deployment-with-immutable-configmap-as-volume.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("deployments-deployment-with-immutable-configmap-as-volume-yaml")' title="复制 deployments/deployment-with-immutable-configmap-as-volume.yaml 到剪贴板"></img></div><div class=includecode id=deployments-deployment-with-immutable-configmap-as-volume-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>apps/v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Deployment<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>immutable-configmap-volume<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app.kubernetes.io/name</span>:<span style=color:#bbb> </span>immutable-configmap-volume<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>replicas</span>:<span style=color:#bbb> </span><span style=color:#666>3</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>matchLabels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app.kubernetes.io/name</span>:<span style=color:#bbb> </span>immutable-configmap-volume<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>template</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app.kubernetes.io/name</span>:<span style=color:#bbb> </span>immutable-configmap-volume<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>alpine<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>alpine:3<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>command</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- /bin/sh<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- -c<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- while true; do echo "$(date) The name of the company is $(cat /etc/config/company_name)";<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>sleep 10; done;<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>containerPort</span>:<span style=color:#bbb> </span><span style=color:#666>80</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumeMounts</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>config-volume<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>mountPath</span>:<span style=color:#bbb> </span>/etc/config<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumes</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>config-volume<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>configMap</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>company-name-20150801</span></span></code></pre></div></div></div><p>创建此 Deployment:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/deployments/deployment-with-immutable-configmap-as-volume.yaml </span></span></code></pre></div><p>检查此 Deployment 的 Pod 以确保它们已就绪(通过<a class=glossary-tooltip title=选择算符允许用户通过标签对一组资源对象进行筛选过滤。 data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/overview/working-with-objects/labels/ target=_blank aria-label=选择算符>选择算符</a>进行匹配):</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods --selector<span style=color:#666>=</span>app.kubernetes.io/name<span style=color:#666>=</span>immutable-configmap-volume </span></span></code></pre></div><p>你应该看到类似以下的输出:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE immutable-configmap-volume-78b6fbff95-5gsfh 1/1 Running 0 62s immutable-configmap-volume-78b6fbff95-7vcj4 1/1 Running 0 62s immutable-configmap-volume-78b6fbff95-vdslm 1/1 Running 0 62s </code></pre><p>Pod 的容器引用 ConfigMap 中所定义的数据,并使用它将报告打印到标准输出。 你可以通过查看 Deployment 中某个 Pod 的日志来检查此报告:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 选择属于该 Deployment 的一个 Pod,并查看其日志</span> </span></span><span style=display:flex><span>kubectl logs deployments/immutable-configmap-volume </span></span></code></pre></div><p>你应该会看到类似以下的输出:</p><pre tabindex=0><code>Found 3 pods, using pod/immutable-configmap-volume-78b6fbff95-5gsfh Wed Mar 20 03:52:34 UTC 2024 The name of the company is ACME, Inc. Wed Mar 20 03:52:44 UTC 2024 The name of the company is ACME, Inc. Wed Mar 20 03:52:54 UTC 2024 The name of the company is ACME, Inc. </code></pre><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>一旦 ConfigMap 被标记为不可变,就无法撤销此变更,也无法修改 <code>data</code> 或 <code>binaryData</code> 字段的内容。<br>为了修改使用此配置的 Pod 的行为,你需要创建一个新的不可变 ConfigMap,并编辑 Deployment 以定义一个稍有不同的 Pod 模板,引用新的 ConfigMap。</p></div><p>通过使用下面所示的清单创建一个新的不可变 ConfigMap:</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/configmap/new-immutable-configmap.yaml download=configmap/new-immutable-configmap.yaml><code>configmap/new-immutable-configmap.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("configmap-new-immutable-configmap-yaml")' title="复制 configmap/new-immutable-configmap.yaml 到剪贴板"></img></div><div class=includecode id=configmap-new-immutable-configmap-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>data</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>company_name</span>:<span style=color:#bbb> </span><span style=color:#b44>"Fiktivesunternehmen GmbH"</span><span style=color:#bbb> </span><span style=color:#080;font-style:italic># 虚构的公司名称</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>ConfigMap<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>immutable</span>:<span style=color:#bbb> </span><span style=color:#a2f;font-weight:700>true</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>company-name-20240312</span></span></code></pre></div></div></div><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/configmap/new-immutable-configmap.yaml </span></span></code></pre></div><p>你应该看到类似以下的输出:</p><pre tabindex=0><code>configmap/company-name-20240312 created </code></pre><p>检查新建的 ConfigMap:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get configmap </span></span></code></pre></div><p>你应该看到输出会同时显示新旧 ConfigMap:</p><pre tabindex=0><code>NAME DATA AGE company-name-20150801 1 22m company-name-20240312 1 24s </code></pre><p>修改 Deployment 以引用新的 ConfigMap。</p><p>编辑 Deployment:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl edit deployment immutable-configmap-volume </span></span></code></pre></div><p>在出现的编辑器中,更新现有的卷定义以使用新的 ConfigMap。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>volumes</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span>- <span style=color:green;font-weight:700>configMap</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>defaultMode</span>:<span style=color:#bbb> </span><span style=color:#666>420</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>company-name-20240312<span style=color:#bbb> </span><span style=color:#080;font-style:italic># 更新此字段</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>config-volume<span style=color:#bbb> </span></span></span></code></pre></div><p>你应该看到以下输出:</p><pre tabindex=0><code>deployment.apps/immutable-configmap-volume edited </code></pre><p>这将触发一次上线操作。等待所有先前的 Pod 终止并且新的 Pod 处于就绪状态。</p><p>监控 Pod 的状态:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods --selector<span style=color:#666>=</span>app.kubernetes.io/name<span style=color:#666>=</span>immutable-configmap-volume </span></span></code></pre></div><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE immutable-configmap-volume-5fdb88fcc8-29v8n 1/1 Running 0 13s immutable-configmap-volume-5fdb88fcc8-52ddd 1/1 Running 0 14s immutable-configmap-volume-5fdb88fcc8-n5jx4 1/1 Running 0 15s immutable-configmap-volume-78b6fbff95-5gsfh 1/1 Terminating 0 32m immutable-configmap-volume-78b6fbff95-7vcj4 1/1 Terminating 0 32m immutable-configmap-volume-78b6fbff95-vdslm 1/1 Terminating 0 32m </code></pre><p>最终,你应该会看到类似以下的输出:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE immutable-configmap-volume-5fdb88fcc8-29v8n 1/1 Running 0 43s immutable-configmap-volume-5fdb88fcc8-52ddd 1/1 Running 0 44s immutable-configmap-volume-5fdb88fcc8-n5jx4 1/1 Running 0 45s </code></pre><p>查看此 Deployment 中某个 Pod 的日志:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 选择属于此 Deployment 的一个 Pod,并查看其日志</span> </span></span><span style=display:flex><span>kubectl logs deployment/immutable-configmap-volume </span></span></code></pre></div><p>你应该会看到类似下面的输出:</p><pre tabindex=0><code>Found 3 pods, using pod/immutable-configmap-volume-5fdb88fcc8-n5jx4 Wed Mar 20 04:24:17 UTC 2024 The name of the company is Fiktivesunternehmen GmbH Wed Mar 20 04:24:27 UTC 2024 The name of the company is Fiktivesunternehmen GmbH Wed Mar 20 04:24:37 UTC 2024 The name of the company is Fiktivesunternehmen GmbH </code></pre><p>建议一旦所有 Deployment 都迁移到使用新的不可变 ConfigMap,删除旧的 ConfigMap。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete configmap company-name-20150801 </span></span></code></pre></div><h2 id=summary>总结</h2><p>在 Pod 上作为卷挂载的 ConfigMap 所发生的变更将在后续的 kubelet 同步后无缝生效。</p><p>配置为 Pod 环境变量的 ConfigMap 所发生变更将在后续的 Pod 上线操作后生效。</p><p>一旦 ConfigMap 被标记为不可变,就无法撤销此变更(你不能将不可变的 ConfigMap 改为可变), 并且你也不能对 <code>data</code> 或 <code>binaryData</code> 字段的内容进行任何变更。你可以删除并重新创建 ConfigMap, 或者你可以创建一个新的不同的 ConfigMap。当你删除 ConfigMap 时, 运行中的容器及其 Pod 将保持对引用了现有 ConfigMap 的任何卷的挂载点。</p><h2 id=清理现场>清理现场</h2><p>终止正在运行的 <code>kubectl port-forward</code> 命令。</p><p>删除以上教程中所创建的资源:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete deployment configmap-volume configmap-env-var configmap-two-containers configmap-sidecar-container immutable-configmap-volume </span></span><span style=display:flex><span>kubectl delete service configmap-service configmap-sidecar-service </span></span><span style=display:flex><span>kubectl delete configmap sport fruits color company-name-20240312 </span></span><span style=display:flex><span> </span></span><span style=display:flex><span>kubectl delete configmap company-name-20150801 <span style=color:#080;font-style:italic># 如果在任务执行期间未被处理</span> </span></span></code></pre></div></div><div class=td-content style=page-break-before:always><h1 id=pg-2efe621cc085b350c8c4574e6f7f1311>3.2 - 使用 ConfigMap 来配置 Redis</h1><p>这篇文档基于<a href=/zh-cn/docs/tasks/configure-pod-container/configure-pod-configmap/>配置 Pod 以使用 ConfigMap</a> 这个任务,提供了一个使用 ConfigMap 来配置 Redis 的真实案例。</p><h2 id=教程目标>教程目标</h2><ul><li>使用 Redis 配置的值创建一个 ConfigMap</li><li>创建一个 Redis Pod,挂载并使用创建的 ConfigMap</li><li>验证配置已经被正确应用</li></ul><h2 id=准备开始>准备开始</h2><p><p>你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 <a href=https://minikube.sigs.k8s.io/docs/tutorials/multi_node/>Minikube</a> 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:</p><ul><li><a href=https://killercoda.com/playgrounds/scenario/kubernetes>Killercoda</a></li><li><a href=https://labs.play-with-k8s.com/>玩转 Kubernetes</a></li></ul>要获知版本信息,请输入 <code>kubectl version</code>.</p><ul><li>此页面上显示的示例适用于 <code>kubectl</code> 1.14 及以上的版本。</li><li>理解<a href=/zh-cn/docs/tasks/configure-pod-container/configure-pod-configmap/>配置 Pod 以使用 ConfigMap</a>。</li></ul><h2 id=real-world-example-configuring-redis-using-a-configmap>真实世界的案例:使用 ConfigMap 来配置 Redis</h2><p>按照下面的步骤,使用 ConfigMap 中的数据来配置 Redis 缓存。</p><p>首先创建一个配置模块为空的 ConfigMap:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>cat <span style=color:#b44><<EOF >./example-redis-config.yaml </span></span></span><span style=display:flex><span><span style=color:#b44>apiVersion: v1 </span></span></span><span style=display:flex><span><span style=color:#b44>kind: ConfigMap </span></span></span><span style=display:flex><span><span style=color:#b44>metadata: </span></span></span><span style=display:flex><span><span style=color:#b44> name: example-redis-config </span></span></span><span style=display:flex><span><span style=color:#b44>data: </span></span></span><span style=display:flex><span><span style=color:#b44> redis-config: "" </span></span></span><span style=display:flex><span><span style=color:#b44>EOF</span> </span></span></code></pre></div><p>应用上面创建的 ConfigMap 以及 Redis Pod 清单:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f example-redis-config.yaml </span></span><span style=display:flex><span>kubectl apply -f https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/pods/config/redis-pod.yaml </span></span></code></pre></div><p>检查 Redis pod 清单的内容,并注意以下几点:</p><ul><li>由 <code>spec.volumes[1]</code> 创建一个名为 <code>config</code> 的卷。</li><li><code>spec.volumes[1].configMap.items[0]</code> 下的 <code>key</code> 和 <code>path</code> 会将来自 <code>example-redis-config</code> ConfigMap 中的 <code>redis-config</code> 键公开在 <code>config</code> 卷上一个名为 <code>redis.conf</code> 的文件中。</li><li>然后 <code>config</code> 卷被 <code>spec.containers[0].volumeMounts[1]</code> 挂载在 <code>/redis-master</code>。</li></ul><p>这样做的最终效果是将上面 <code>example-redis-config</code> 配置中 <code>data.redis-config</code> 的数据作为 Pod 中的 <code>/redis-master/redis.conf</code> 公开。</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/pods/config/redis-pod.yaml download=pods/config/redis-pod.yaml><code>pods/config/redis-pod.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("pods-config-redis-pod-yaml")' title="复制 pods/config/redis-pod.yaml 到剪贴板"></img></div><div class=includecode id=pods-config-redis-pod-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Pod<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>redis<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>redis<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>redis:5.0.4<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>command</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- redis-server<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:#b44>"/redis-master/redis.conf"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>env</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>MASTER<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>value</span>:<span style=color:#bbb> </span><span style=color:#b44>"true"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>containerPort</span>:<span style=color:#bbb> </span><span style=color:#666>6379</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>resources</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>limits</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>cpu</span>:<span style=color:#bbb> </span><span style=color:#b44>"0.1"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumeMounts</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>mountPath</span>:<span style=color:#bbb> </span>/redis-master-data<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>data<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>mountPath</span>:<span style=color:#bbb> </span>/redis-master<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>config<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumes</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>data<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>emptyDir</span>:<span style=color:#bbb> </span>{}<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>config<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>configMap</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>example-redis-config<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>items</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>key</span>:<span style=color:#bbb> </span>redis-config<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>path</span>:<span style=color:#bbb> </span>redis.conf<span style=color:#bbb> </span></span></span></code></pre></div></div></div><p>检查创建的对象:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pod/redis configmap/example-redis-config </span></span></code></pre></div><p>你应该可以看到以下输出:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE pod/redis 1/1 Running 0 8s NAME DATA AGE configmap/example-redis-config 1 14s </code></pre><p>回顾一下,我们在 <code>example-redis-config</code> ConfigMap 保留了空的 <code>redis-config</code> 键:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl describe configmap/example-redis-config </span></span></code></pre></div><p>你应该可以看到一个空的 <code>redis-config</code> 键:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>Name: example-redis-config </span></span><span style=display:flex><span>Namespace: default </span></span><span style=display:flex><span>Labels: <none> </span></span><span style=display:flex><span>Annotations: <none> </span></span><span style=display:flex><span> </span></span><span style=display:flex><span><span style=color:#b8860b>Data</span> </span></span><span style=display:flex><span><span style=color:#666>====</span> </span></span><span style=display:flex><span>redis-config: </span></span></code></pre></div><p>使用 <code>kubectl exec</code> 进入 pod,运行 <code>redis-cli</code> 工具检查当前配置:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl <span style=color:#a2f>exec</span> -it redis -- redis-cli </span></span></code></pre></div><p>查看 <code>maxmemory</code>:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>127.0.0.1:6379> CONFIG GET maxmemory </span></span></code></pre></div><p>它应该显示默认值 0:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>1<span style=color:#666>)</span> <span style=color:#b44>"maxmemory"</span> </span></span><span style=display:flex><span>2<span style=color:#666>)</span> <span style=color:#b44>"0"</span> </span></span></code></pre></div><p>同样,查看 <code>maxmemory-policy</code>:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>127.0.0.1:6379> CONFIG GET maxmemory-policy </span></span></code></pre></div><p>它也应该显示默认值 <code>noeviction</code>:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>1<span style=color:#666>)</span> <span style=color:#b44>"maxmemory-policy"</span> </span></span><span style=display:flex><span>2<span style=color:#666>)</span> <span style=color:#b44>"noeviction"</span> </span></span></code></pre></div><p>现在,向 <code>example-redis-config</code> ConfigMap 添加一些配置:</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/pods/config/example-redis-config.yaml download=pods/config/example-redis-config.yaml><code>pods/config/example-redis-config.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("pods-config-example-redis-config-yaml")' title="复制 pods/config/example-redis-config.yaml 到剪贴板"></img></div><div class=includecode id=pods-config-example-redis-config-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>ConfigMap<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>example-redis-config<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>data</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>redis-config</span>:<span style=color:#bbb> </span>|<span style=color:#b44;font-style:italic> </span></span></span><span style=display:flex><span><span style=color:#b44;font-style:italic> maxmemory 2mb </span></span></span><span style=display:flex><span><span style=color:#b44;font-style:italic> maxmemory-policy allkeys-lru</span><span style=color:#bbb> </span></span></span></code></pre></div></div></div><p>应用更新的 ConfigMap:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f example-redis-config.yaml </span></span></code></pre></div><p>确认 ConfigMap 已更新:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl describe configmap/example-redis-config </span></span></code></pre></div><p>你应该可以看到我们刚刚添加的配置:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>Name: example-redis-config </span></span><span style=display:flex><span>Namespace: default </span></span><span style=display:flex><span>Labels: <none> </span></span><span style=display:flex><span>Annotations: <none> </span></span><span style=display:flex><span> </span></span><span style=display:flex><span><span style=color:#b8860b>Data</span> </span></span><span style=display:flex><span><span style=color:#666>====</span> </span></span><span style=display:flex><span>redis-config: </span></span><span style=display:flex><span>---- </span></span><span style=display:flex><span>maxmemory 2mb </span></span><span style=display:flex><span>maxmemory-policy allkeys-lru </span></span></code></pre></div><p>通过 <code>kubectl exec</code> 使用 <code>redis-cli</code> 再次检查 Redis Pod,查看是否已应用配置:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl <span style=color:#a2f>exec</span> -it redis -- redis-cli </span></span></code></pre></div><p>查看 <code>maxmemory</code>:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>127.0.0.1:6379> CONFIG GET maxmemory </span></span></code></pre></div><p>它保持默认值 0:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>1<span style=color:#666>)</span> <span style=color:#b44>"maxmemory"</span> </span></span><span style=display:flex><span>2<span style=color:#666>)</span> <span style=color:#b44>"0"</span> </span></span></code></pre></div><p>同样,<code>maxmemory-policy</code> 保留为默认设置 <code>noeviction</code>:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>127.0.0.1:6379> CONFIG GET maxmemory-policy </span></span></code></pre></div><p>返回:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>1<span style=color:#666>)</span> <span style=color:#b44>"maxmemory-policy"</span> </span></span><span style=display:flex><span>2<span style=color:#666>)</span> <span style=color:#b44>"noeviction"</span> </span></span></code></pre></div><p>配置值未更改,因为需要重新启动 Pod 才能从关联的 ConfigMap 中获取更新的值。 让我们删除并重新创建 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete pod redis </span></span><span style=display:flex><span>kubectl apply -f https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/pods/config/redis-pod.yaml </span></span></code></pre></div><p>现在,最后一次重新检查配置值:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl <span style=color:#a2f>exec</span> -it redis -- redis-cli </span></span></code></pre></div><p>查看 <code>maxmemory</code>:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>127.0.0.1:6379> CONFIG GET maxmemory </span></span></code></pre></div><p>现在,它应该返回更新后的值 2097152:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>1<span style=color:#666>)</span> <span style=color:#b44>"maxmemory"</span> </span></span><span style=display:flex><span>2<span style=color:#666>)</span> <span style=color:#b44>"2097152"</span> </span></span></code></pre></div><p>同样,<code>maxmemory-policy</code> 也已更新:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>127.0.0.1:6379> CONFIG GET maxmemory-policy </span></span></code></pre></div><p>现在它反映了期望值 <code>allkeys-lru</code>:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>1<span style=color:#666>)</span> <span style=color:#b44>"maxmemory-policy"</span> </span></span><span style=display:flex><span>2<span style=color:#666>)</span> <span style=color:#b44>"allkeys-lru"</span> </span></span></code></pre></div><p>删除创建的资源,清理你的工作:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete pod/redis configmap/example-redis-config </span></span></code></pre></div><h2 id=接下来>接下来</h2><ul><li>了解有关 <a href=/zh-cn/docs/tasks/configure-pod-container/configure-pod-configmap/>ConfigMap</a> 的更多信息。</li><li>学习<a href=/zh-cn/docs/tutorials/configuration/updating-configuration-via-a-configmap/>通过 ConfigMap 更新配置</a>的示例。</li></ul></div><div class=td-content style=page-break-before:always><h1 id=pg-f7dcafe033a10939f2a0291617626575>3.3 - 使用边车(Sidecar)容器</h1><p>本文适用于使用新的内置<a href=/zh-cn/docs/concepts/workloads/pods/sidecar-containers/>边车容器</a>特性的用户。</p><p>边车容器并不是一个新概念,正如在<a href=/blog/2015/06/the-distributed-system-toolkit-patterns/>博客文章</a>中所提到的那样。 Kubernetes 允许在一个 Pod 中运行多个容器来实现这一概念。然而,作为一个普通容器运行边车容器存在许多限制, 这些限制通过新的内置边车容器支持得到了解决。</p><div class="feature-state-notice feature-beta" title="特性门控: SidecarContainers"><span class=feature-state-name>特性状态:</span> <code>Kubernetes v1.29 [beta]</code> (enabled by default: true)</div><h2 id=教程目标>教程目标</h2><ul><li>理解边车容器的需求</li><li>能够排查边车容器的问题</li><li>了解如何"注入"边车容器到任意的工作负载中</li></ul><h2 id=准备开始>准备开始</h2><p><p>你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 <a href=https://minikube.sigs.k8s.io/docs/tutorials/multi_node/>Minikube</a> 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:</p><ul><li><a href=https://killercoda.com/playgrounds/scenario/kubernetes>Killercoda</a></li><li><a href=https://labs.play-with-k8s.com/>玩转 Kubernetes</a></li></ul>你的 Kubernetes 服务器版本必须不低于版本 1.29. 要获知版本信息,请输入 <code>kubectl version</code>.</p><h2 id=边车容器概述>边车容器概述</h2><p>边车容器是与主应用程序容器在同一 <a class=glossary-tooltip title='Pod 表示你的集群上一组正在运行的容器。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/workloads/pods/ target=_blank aria-label=Pod>Pod</a> 内一起运行的辅助容器。这些容器通过提供额外的服务或功能(如日志记录、监控、安全或数据同步)来增强或扩展主<strong>应用容器</strong>的功能, 而无需直接修改主应用程序代码。你可以在<a href=/zh-cn/docs/concepts/workloads/pods/sidecar-containers/>边车容器</a>概念页面中阅读更多相关内容。</p><p>边车容器的概念并不新鲜,有许多不同的实现方式。除了你(定义 Pod 的人)希望运行的边车容器外, 一些<a class=glossary-tooltip title='扩展 Kubernetes 功能的资源。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/cluster-administration/addons/ target=_blank aria-label=插件>插件</a>也会在 Pod 开始运行之前对其进行修改, 以添加额外的边车容器。这些额外边车容器的<strong>注入</strong>机制通常是<a href=/zh-cn/docs/reference/access-authn-authz/admission-controllers/#mutatingadmissionwebhook>变更 Webhook(Mutating Webhook)</a>。 例如,服务网格插件可能会注入一个配置双向 TLS(Mutual TLS)和传输中加密的边车容器。</p><p>虽然边车容器的概念并不新鲜,但 Kubernetes 对这一特性的原生实现却是新的。 与每一项新特性一样,采用这一特性可能会带来某些挑战。</p><p>本教程探讨了终端用户和边车容器作者可能遇到的挑战及其解决方案。</p><h2 id=内置边车容器的优势>内置边车容器的优势</h2><p>使用 Kubernetes 对边车容器的原生支持可以带来以下几个好处:</p><ol><li>你可以配置原生边车容器在 <a class=glossary-tooltip title='应用容器运行前必须先运行完成的一个或多个 Init 容器(Init Container)。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/workloads/pods/init-containers/ target=_blank aria-label='Init 容器'>Init 容器</a>之前启动。</li><li>内置边车容器可以编写为确保它们最后终止。一旦所有常规容器完成并终止,边车容器将接收到 <code>SIGTERM</code> 信号。 如果边车容器未能体面关闭,系统将使用 <code>SIGKILL</code> 信号终止它。</li><li>在 Job 中,当 Pod 配置 <code>restartPolicy: OnFailure</code> 或 <code>restartPolicy: Never</code> 时, 原生边车容器不会阻止 Pod 完成。而对于传统边车容器,需要特别处理这种情况。</li><li>同样在 Job 中,即使 Pod 的 <code>restartPolicy: Never</code> 时常规容器不会重启, 内置边车容器仍会在完成后继续重启。</li></ol><p>更多详情请参见<a href=/zh-cn/docs/concepts/workloads/pods/sidecar-containers/#differences-from-application-containers>与 Init 容器的区别</a>。</p><h2 id=采用内置边车容器>采用内置边车容器</h2><p>从 Kubernetes 1.29 版本开始,<code>SidecarContainers</code> <a href=/zh-cn/docs/reference/command-line-tools-reference/feature-gates/>特性门控</a>处于 Beta 阶段, 并默认启用。某些集群可能禁用了此特性,或者安装了与该特性不兼容的软件。</p><p>当这种情况发生时,Pod 可能会被拒绝,或者边车容器可能阻止 Pod 启动,导致 Pod 无法使用。 这种情况下很容易检测到问题,因为 Pod 会卡在初始化阶段。然而,通常不清楚是什么原因导致了问题。</p><p>以下是在使用边车容器处理工作负载时可以考虑的因素和排查步骤。</p><h3 id=确保特性门控已启用>确保特性门控已启用</h3><p>首先,确保 API 服务器和节点都在 Kubernetes v1.29 及更高版本上运行。 如果节点运行的是早期版本且未启用该特性,集群中的该特性将无法正常工作。</p><div class="alert alert-info" role=alert><h4 class=alert-heading>注意</h4><p>此特性可以在 1.28 版本的节点上启用。然而,内置边车容器的终止行为在 1.28 版本中有所不同, 不建议将边车的行为调整为 1.28 中的行为。但是,如果唯一的关注点是启动顺序, 上述陈述可以改为:运行 1.28 版本并启用了特性门控的节点。</p></div><p>你应该确保控制平面内的 API 服务器<strong>和</strong>所有节点都启用了特性门控。</p><p>一种检查特性门控是否启用的方法是运行如下命令:</p><ul><li><p>对于 API 服务器:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get --raw /metrics | grep kubernetes_feature_enabled | grep SidecarContainers </span></span></code></pre></div></li></ul><ul><li><p>对于单个节点:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get --raw /api/v1/nodes/<node-name>/proxy/metrics | grep kubernetes_feature_enabled | grep SidecarContainers </span></span></code></pre></div></li></ul><p>如果你看到类似这样的内容:</p><pre tabindex=0><code>kubernetes_feature_enabled{name="SidecarContainers",stage="BETA"} 1 </code></pre><p>表示该特性已启用。</p><h3 id=检查第三方工具和变更-webhook>检查第三方工具和变更 Webhook</h3><p>如果你在验证特性时遇到问题,这可能表明某个第三方工具或变更 Webhook 出现了问题。</p><p>当 <code>SidecarContainers</code> 特性门控启用后,Pod 在其 API 中会新增一个字段。 某些工具或变更 Webhook 可能是基于早期版本的 Kubernetes API 构建的。</p><p>如果工具使用各种修补策略将未知字段原样传递,这不会有问题。然而,有些工具会删除未知字段; 如果你使用的是这些工具,必须使用 v1.28+ 版本的 Kubernetes API 客户端代码重新编译它们。</p><p>检查这一点的方法是使用 <code>kubectl describe pod</code> 命令查看已通过变更准入的 Pod。 如果任何工具删除了新字段(如 <code>restartPolicy: Always</code>),你将不会在命令输出中看到它。</p><p>如果你遇到了此类问题,请告知工具或 Webhook 的作者使用修补策略来修改对象,而不是进行完整的对象更新。</p><div class="alert alert-info" role=alert><h4 class=alert-heading>注意</h4><p>变更 Webhook 可能会根据某些条件更新 Pod。 因此,边车容器可能对某些 Pod 有效,但对其他 Pod 无效。</p></div><h3 id=边车的自动注入>边车的自动注入</h3><p>如果你使用的是自动注入边车的软件,可以采取几种策略来确保能够使用原生边车容器。 所有这些策略通常都是你可以选择的选项,以决定注入边车的 Pod 是否会落在支持该特性的节点上。</p><p>例如,可以参考 <a href=https://github.com/istio/istio/issues/48794>Istio 社区中的这次讨论</a>。 讨论中探讨了以下选项:</p><ol><li>标记支持边车的节点上的 Pod。你可以使用节点标签和节点亲和性来标记支持边车容器的节点以及落在这些节点上的 Pod。</li></ol><ol start=2><li>注入时检查节点兼容性。在边车注入过程中,可以使用以下策略来检查节点兼容性:<ul><li>查询节点版本并假设版本 1.29+ 上启用了特性门控。</li><li>查询节点 Prometheus 指标并检查特性启用状态。</li><li>假设节点与 API 服务器的版本差异在<a href=/zh-cn/releases/version-skew-policy/#supported-version-skew>支持的版本范围</a>内。</li><li>可能还有其他自定义方法来检测节点兼容性。</li></ul></li></ol><ol start=3><li>开发通用边车注入器(Sidecar Injector)。通用边车注入器的想法是在注入一个普通容器的同时注入一个原生边车容器,并在运行时决定哪个容器会生效。 通用边车注入器虽然浪费资源(因为它会两次计算请求量),但在某些特殊情况下可以视为可行的解决方案。<ul><li>一种方法是在原生边车容器启动时检测节点版本,如果不支持边车特性则立即退出。</li><li>考虑运行时特性检测设计:<ul><li>定义一个空目录(Empty Dir)以便容器之间通信。</li><li>注入一个 Init 容器,我们称之为 <code>NativeSidecar</code>,并设置 <code>restartPolicy=Always</code>。</li><li><code>NativeSidecar</code> 必须在空目录中写入一个文件,表示首次运行并立即退出,退出码为 <code>0</code>。</li></ul><pre><code> - `NativeSidecar` 在重启时(当支持原生边车时)检查空目录中是否已存在该文件,并进行更改 —— 表示已支持原生边车容器并正在运行。 </code></pre><ul><li>注入一个普通容器,我们称之为 <code>OldWaySidecar</code>。</li><li><code>OldWaySidecar</code> 启动时检查空目录中是否存在文件。</li><li>如果文件表示 <code>NativeSidecar</code> 未运行,则假设边特性不支持,并按边车的方式工作。</li><li>如果文件表示 <code>NativeSidecar</code> 正在运行,则根据 Pod 的 <code>restartPolicy</code> 决定行为:</li><li>如果 Pod 的 <code>restartPolicy=Always</code>,则不做任何操作并永远休眠。</li><li>如果 Pod 的 <code>restartPolicy!=Always</code>,则立即退出,退出码为 <code>0</code>。</li></ul></li></ul></li></ol><h2 id=接下来>接下来</h2><ul><li>了解有关<a href=/zh-cn/docs/concepts/workloads/pods/sidecar-containers/>边车容器</a>的更多信息。</li></ul></div><div class=td-content style=page-break-before:always><h1 id=pg-fe7e92bed8fb92872b139f12c4568cdb>4 - 安全</h1><p>对于运行 Kubernetes 集群的大多数组织和人员来说,安全是一个重要问题。 你可以在 Kubernetes 文档的其他地方找到基本的<a href=/zh-cn/docs/concepts/security/security-checklist/>安全检查清单</a>。</p><p>要了解如何部署和管理 Kubernetes 的安全的方方面面,你可以按照本部分中的教程进行操作。</p></div><div class=td-content style=page-break-before:always><h1 id=pg-d5f847bcdb6f7efbfc9c8a180d73e29a>4.1 - 在集群级别应用 Pod 安全标准</h1><div class="alert alert-primary" role=alert><h4 class=alert-heading>Note</h4><p>本教程仅适用于新集群。</p></div><p>Pod 安全是一个准入控制器,当新的 Pod 被创建时,它会根据 Kubernetes <a href=/zh-cn/docs/concepts/security/pod-security-standards/>Pod 安全标准</a> 进行检查。这是在 v1.25 中达到正式发布(GA)的功能。 本教程将向你展示如何在集群级别实施 <code>baseline</code> Pod 安全标准, 该标准将标准配置应用于集群中的所有名字空间。</p><p>要将 Pod 安全标准应用于特定名字空间, 请参阅<a href=/zh-cn/docs/tutorials/security/ns-level-pss>在名字空间级别应用 Pod 安全标准</a>。</p><p>如果你正在运行 v1.31 以外的 Kubernetes 版本, 请查阅该版本的文档。</p><h2 id=准备开始>准备开始</h2><p>在你的工作站中安装以下内容:</p><ul><li><a href=https://kind.sigs.k8s.io/docs/user/quick-start/#installation>kind</a></li><li><a href=/zh-cn/docs/tasks/tools/>kubectl</a></li></ul><p>本教程演示了你可以对完全由你控制的 Kubernetes 集群所配置的内容。 如果你正在学习如何为一个无法配置控制平面的托管集群配置 Pod 安全准入, 请参阅<a href=/zh-cn/docs/tutorials/security/ns-level-pss>在名字空间级别应用 Pod 安全标准</a>。</p><h2 id=choose-the-right-pod-security-standard-to-apply>正确选择要应用的 Pod 安全标准</h2><p><a href=/zh-cn/docs/concepts/security/pod-security-admission/>Pod 安全准入</a> 允许你使用以下模式应用内置的 <a href=/zh-cn/docs/concepts/security/pod-security-standards/>Pod 安全标准</a>: <code>enforce</code>、<code>audit</code> 和 <code>warn</code>。</p><p>要收集信息以便选择最适合你的配置的 Pod 安全标准,请执行以下操作:</p><ol><li><p>创建一个没有应用 Pod 安全标准的集群:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kind create cluster --name psa-wo-cluster-pss </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>Creating cluster "psa-wo-cluster-pss" ... ✓ Ensuring node image (kindest/node:v1.31.0) 🖼 ✓ Preparing nodes 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 Set kubectl context to "kind-psa-wo-cluster-pss" You can now use your cluster with: kubectl cluster-info --context kind-psa-wo-cluster-pss Thanks for using kind! 😊 </code></pre></li></ol><ol start=2><li><p>将 kubectl 上下文设置为新集群:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl cluster-info --context kind-psa-wo-cluster-pss </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>Kubernetes control plane is running at https://127.0.0.1:61350 CoreDNS is running at https://127.0.0.1:61350/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. </code></pre></li></ol><ol start=3><li><p>获取集群中的名字空间列表:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get ns </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>NAME STATUS AGE default Active 9m30s kube-node-lease Active 9m32s kube-public Active 9m32s kube-system Active 9m32s local-path-storage Active 9m26s </code></pre></li></ol><ol start=4><li><p>使用 <code>--dry-run=server</code> 来了解应用不同的 Pod 安全标准时会发生什么:</p><ol><li><p>Privileged</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl label --dry-run<span style=color:#666>=</span>server --overwrite ns --all <span style=color:#b62;font-weight:700>\ </span></span></span><span style=display:flex><span><span style=color:#b62;font-weight:700></span>pod-security.kubernetes.io/enforce<span style=color:#666>=</span>privileged </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>namespace/default labeled namespace/kube-node-lease labeled namespace/kube-public labeled namespace/kube-system labeled namespace/local-path-storage labeled </code></pre></li><li><p>Baseline</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl label --dry-run<span style=color:#666>=</span>server --overwrite ns --all <span style=color:#b62;font-weight:700>\ </span></span></span><span style=display:flex><span><span style=color:#b62;font-weight:700></span>pod-security.kubernetes.io/enforce<span style=color:#666>=</span>baseline </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>namespace/default labeled namespace/kube-node-lease labeled namespace/kube-public labeled Warning: existing pods in namespace "kube-system" violate the new PodSecurity enforce level "baseline:latest" Warning: etcd-psa-wo-cluster-pss-control-plane (and 3 other pods): host namespaces, hostPath volumes Warning: kindnet-vzj42: non-default capabilities, host namespaces, hostPath volumes Warning: kube-proxy-m6hwf: host namespaces, hostPath volumes, privileged namespace/kube-system labeled namespace/local-path-storage labeled </code></pre></li><li><p>Restricted</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl label --dry-run<span style=color:#666>=</span>server --overwrite ns --all <span style=color:#b62;font-weight:700>\ </span></span></span><span style=display:flex><span><span style=color:#b62;font-weight:700></span>pod-security.kubernetes.io/enforce<span style=color:#666>=</span>restricted </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>namespace/default labeled namespace/kube-node-lease labeled namespace/kube-public labeled Warning: existing pods in namespace "kube-system" violate the new PodSecurity enforce level "restricted:latest" Warning: coredns-7bb9c7b568-hsptc (and 1 other pod): unrestricted capabilities, runAsNonRoot != true, seccompProfile Warning: etcd-psa-wo-cluster-pss-control-plane (and 3 other pods): host namespaces, hostPath volumes, allowPrivilegeEscalation != false, unrestricted capabilities, restricted volume types, runAsNonRoot != true Warning: kindnet-vzj42: non-default capabilities, host namespaces, hostPath volumes, allowPrivilegeEscalation != false, unrestricted capabilities, restricted volume types, runAsNonRoot != true, seccompProfile Warning: kube-proxy-m6hwf: host namespaces, hostPath volumes, privileged, allowPrivilegeEscalation != false, unrestricted capabilities, restricted volume types, runAsNonRoot != true, seccompProfile namespace/kube-system labeled Warning: existing pods in namespace "local-path-storage" violate the new PodSecurity enforce level "restricted:latest" Warning: local-path-provisioner-d6d9f7ffc-lw9lh: allowPrivilegeEscalation != false, unrestricted capabilities, runAsNonRoot != true, seccompProfile namespace/local-path-storage labeled </code></pre></li></ol></li></ol><p>从前面的输出中,你会注意到应用 <code>privileged</code> Pod 安全标准不会显示任何名字空间的警告。 然而,<code>baseline</code> 和 <code>restricted</code> 标准都有警告,特别是在 <code>kube-system</code> 名字空间中。</p><h2 id=set-modes-versions-and-standards>设置模式、版本和标准</h2><p>在本节中,你将以下 Pod 安全标准应用于最新(<code>latest</code>)版本:</p><ul><li>在 <code>enforce</code> 模式下的 <code>baseline</code> 标准。</li><li><code>warn</code> 和 <code>audit</code> 模式下的 <code>restricted</code> 标准。</li></ul><p><code>baseline</code> Pod 安全标准提供了一个方便的中间立场,能够保持豁免列表简短并防止已知的特权升级。</p><p>此外,为了防止 <code>kube-system</code> 中的 Pod 失败,你将免除该名字空间应用 Pod 安全标准。</p><p>在你自己的环境中实施 Pod 安全准入时,请考虑以下事项:</p><ol><li><p>根据应用于集群的风险状况,更严格的 Pod 安全标准(如 <code>restricted</code>)可能是更好的选择。</p></li><li><p>对 <code>kube-system</code> 名字空间进行赦免会允许 Pod 在其中以 <code>privileged</code> 模式运行。 对于实际使用,Kubernetes 项目强烈建议你应用严格的 RBAC 策略来限制对 <code>kube-system</code> 的访问, 遵循最小特权原则。</p></li><li><p>创建一个配置文件,Pod 安全准入控制器可以使用该文件来实现这些 Pod 安全标准:</p><pre tabindex=0><code>mkdir -p /tmp/pss cat <<EOF > /tmp/pss/cluster-level-pss.yaml apiVersion: apiserver.config.k8s.io/v1 kind: AdmissionConfiguration plugins: - name: PodSecurity configuration: apiVersion: pod-security.admission.config.k8s.io/v1 kind: PodSecurityConfiguration defaults: enforce: "baseline" enforce-version: "latest" audit: "restricted" audit-version: "latest" warn: "restricted" warn-version: "latest" exemptions: usernames: [] runtimeClasses: [] namespaces: [kube-system] EOF </code></pre><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p><code>pod-security.admission.config.k8s.io/v1</code> 配置需要 v1.25+。 对于 v1.23 和 v1.24,使用 <a href=https://v1-24.docs.kubernetes.io/zh-cn/docs/tasks/configure-pod-container/enforce-standards-admission-controller/>v1beta1</a>。 对于 v1.22,使用 <a href=https://v1-22.docs.kubernetes.io/docs/tasks/configure-pod-container/enforce-standards-admission-controller/>v1alpha1</a>。</p></div></li></ol><ol start=4><li><p>在创建集群时配置 API 服务器使用此文件:</p><pre tabindex=0><code>cat <<EOF > /tmp/pss/cluster-config.yaml kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane kubeadmConfigPatches: - | kind: ClusterConfiguration apiServer: extraArgs: admission-control-config-file: /etc/config/cluster-level-pss.yaml extraVolumes: - name: accf hostPath: /etc/config mountPath: /etc/config readOnly: false pathType: "DirectoryOrCreate" extraMounts: - hostPath: /tmp/pss containerPath: /etc/config # optional: if set, the mount is read-only. # default false readOnly: false # optional: if set, the mount needs SELinux relabeling. # default false selinuxRelabel: false # optional: set propagation mode (None, HostToContainer or Bidirectional) # see https://kubernetes.io/docs/concepts/storage/volumes/#mount-propagation # default None propagation: None EOF </code></pre><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>如果你在 macOS 上使用 Docker Desktop 和 kind, 你可以在菜单项 <strong>Preferences > Resources > File Sharing</strong> 下添加 <code>/tmp</code> 作为共享目录。</p></div></li></ol><ol start=5><li><p>创建一个使用 Pod 安全准入的集群来应用这些 Pod 安全标准:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kind create cluster --name psa-with-cluster-pss --config /tmp/pss/cluster-config.yaml </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>Creating cluster "psa-with-cluster-pss" ... ✓ Ensuring node image (kindest/node:v1.31.0) 🖼 ✓ Preparing nodes 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 Set kubectl context to "kind-psa-with-cluster-pss" You can now use your cluster with: kubectl cluster-info --context kind-psa-with-cluster-pss Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂 </code></pre></li></ol><ol start=6><li><p>将 kubectl 指向集群:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl cluster-info --context kind-psa-with-cluster-pss </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>Kubernetes control plane is running at https://127.0.0.1:63855 CoreDNS is running at https://127.0.0.1:63855/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. </code></pre></li></ol><ol start=7><li><p>在 default 名字空间下创建一个 Pod:</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/security/example-baseline-pod.yaml download=security/example-baseline-pod.yaml><code>security/example-baseline-pod.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("security-example-baseline-pod-yaml")' title="复制 security/example-baseline-pod.yaml 到剪贴板"></img></div><div class=includecode id=security-example-baseline-pod-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Pod<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>containerPort</span>:<span style=color:#bbb> </span><span style=color:#666>80</span><span style=color:#bbb> </span></span></span></code></pre></div></div></div><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/security/example-baseline-pod.yaml </span></span></code></pre></div><p>这个 Pod 正常启动,但输出包含警告:</p><pre tabindex=0><code>Warning: would violate PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "nginx" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "nginx" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "nginx" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "nginx" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost") pod/nginx created </code></pre></li></ol><h2 id=clean-up>清理</h2><p>现在通过运行以下命令删除你上面创建的集群:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kind delete cluster --name psa-with-cluster-pss </span></span></code></pre></div><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kind delete cluster --name psa-wo-cluster-pss </span></span></code></pre></div><h2 id=接下来>接下来</h2><ul><li>运行一个 <a href=/zh-cn/examples/security/kind-with-cluster-level-baseline-pod-security.sh>shell 脚本</a> 一次执行前面的所有步骤:<ol><li>创建一个基于 Pod 安全标准的集群级别配置</li><li>创建一个文件让 API 服务器消费这个配置</li><li>创建一个集群,用这个配置创建一个 API 服务器</li><li>设置 kubectl 上下文为这个新集群</li><li>创建一个最小的 Pod yaml 文件</li><li>应用这个文件,在新集群中创建一个 Pod</li></ol></li><li><a href=/zh-cn/docs/concepts/security/pod-security-admission/>Pod 安全准入</a></li><li><a href=/zh-cn/docs/concepts/security/pod-security-standards/>Pod 安全标准</a></li><li><a href=/zh-cn/docs/tutorials/security/ns-level-pss/>在名字空间级别应用 Pod 安全标准</a></li></ul></div><div class=td-content style=page-break-before:always><h1 id=pg-31a6c137cfc5bfea9d88f4b109109465>4.2 - 在名字空间级别应用 Pod 安全标准</h1><div class="alert alert-primary" role=alert><h4 class=alert-heading>Note</h4><p>本教程仅适用于新集群。</p></div><p>Pod Security Admission 是一个准入控制器,在创建 Pod 时应用 <a href=/zh-cn/docs/concepts/security/pod-security-standards/>Pod 安全标准</a>。 这是在 v1.25 中达到正式发布(GA)的功能。 在本教程中,你将应用 <code>baseline</code> Pod 安全标准,每次一个名字空间。</p><p>你还可以在集群级别一次将 Pod 安全标准应用于多个名称空间。 有关说明,请参阅<a href=/zh-cn/docs/tutorials/security/cluster-level-pss/>在集群级别应用 Pod 安全标准</a>。</p><h2 id=准备开始>准备开始</h2><p>在你的工作站中安装以下内容:</p><ul><li><a href=https://kind.sigs.k8s.io/docs/user/quick-start/#installation>kind</a></li><li><a href=/zh-cn/docs/tasks/tools/>kubectl</a></li></ul><h2 id=create-cluster>创建集群</h2><ol start=2><li><p>按照如下方式创建一个 <code>kind</code> 集群:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kind create cluster --name psa-ns-level </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>Creating cluster "psa-ns-level" ... ✓ Ensuring node image (kindest/node:v1.31.0) 🖼 ✓ Preparing nodes 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 Set kubectl context to "kind-psa-ns-level" You can now use your cluster with: kubectl cluster-info --context kind-psa-ns-level Not sure what to do next? 😅 Check out https://kind.sigs.k8s.io/docs/user/quick-start/ </code></pre></li></ol><ol><li><p>将 kubectl 上下文设置为新集群:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl cluster-info --context kind-psa-ns-level </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>Kubernetes control plane is running at https://127.0.0.1:50996 CoreDNS is running at https://127.0.0.1:50996/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. </code></pre></li></ol><h2 id=create-a-namespace>创建名字空间</h2><p>创建一个名为 <code>example</code> 的新名字空间:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl create ns example </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>namespace/example created </code></pre><h2 id=enable-pod-security-standards-checking-for-that-namespace>为该命名空间启用 Pod 安全标准检查</h2><ol><li><p>使用内置 Pod 安全准入所支持的标签在此名字空间上启用 Pod 安全标准。 在这一步中,我们将根据最新版本(默认值)对基线 Pod 安全标准发出警告。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl label --overwrite ns example <span style=color:#b62;font-weight:700>\ </span></span></span><span style=display:flex><span><span style=color:#b62;font-weight:700></span> pod-security.kubernetes.io/warn<span style=color:#666>=</span>baseline <span style=color:#b62;font-weight:700>\ </span></span></span><span style=display:flex><span><span style=color:#b62;font-weight:700></span> pod-security.kubernetes.io/warn-version<span style=color:#666>=</span>latest </span></span></code></pre></div></li></ol><ol><li><p>你可以使用标签在任何名字空间上配置多个 Pod 安全标准检查。 以下命令将强制(<code>enforce</code>) 执行基线(<code>baseline</code>)Pod 安全标准, 但根据最新版本(默认值)对受限(<code>restricted</code>)Pod 安全标准执行警告(<code>warn</code>)和审核(<code>audit</code>)。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl label --overwrite ns example <span style=color:#b62;font-weight:700>\ </span></span></span><span style=display:flex><span><span style=color:#b62;font-weight:700></span> pod-security.kubernetes.io/enforce<span style=color:#666>=</span>baseline <span style=color:#b62;font-weight:700>\ </span></span></span><span style=display:flex><span><span style=color:#b62;font-weight:700></span> pod-security.kubernetes.io/enforce-version<span style=color:#666>=</span>latest <span style=color:#b62;font-weight:700>\ </span></span></span><span style=display:flex><span><span style=color:#b62;font-weight:700></span> pod-security.kubernetes.io/warn<span style=color:#666>=</span>restricted <span style=color:#b62;font-weight:700>\ </span></span></span><span style=display:flex><span><span style=color:#b62;font-weight:700></span> pod-security.kubernetes.io/warn-version<span style=color:#666>=</span>latest <span style=color:#b62;font-weight:700>\ </span></span></span><span style=display:flex><span><span style=color:#b62;font-weight:700></span> pod-security.kubernetes.io/audit<span style=color:#666>=</span>restricted <span style=color:#b62;font-weight:700>\ </span></span></span><span style=display:flex><span><span style=color:#b62;font-weight:700></span> pod-security.kubernetes.io/audit-version<span style=color:#666>=</span>latest </span></span></code></pre></div></li></ol><h2 id=verify-the-pod-security-standards>验证 Pod 安全标准</h2><ol><li><p>在 <code>example</code> 名字空间中创建一个基线 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -n example -f https://k8s.io/examples/security/example-baseline-pod.yaml </span></span></code></pre></div><p>Pod 确实启动正常;输出包括一条警告信息。例如:</p><pre tabindex=0><code>Warning: would violate PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "nginx" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "nginx" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "nginx" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "nginx" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost") pod/nginx created </code></pre></li></ol><ol><li><p>在 <code>default</code> 名字空间中创建一个基线 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -n default -f https://k8s.io/examples/security/example-baseline-pod.yaml </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>pod/nginx created </code></pre></li></ol><p>Pod 安全标准实施和警告设置仅被应用到 <code>example</code> 名字空间。 以上 Pod 安全标准仅被应用到 <code>example</code> 名字空间。 你可以在没有警告的情况下在 <code>default</code> 名字空间中创建相同的 Pod。</p><h2 id=clean-up>清理</h2><p>现在通过运行以下命令删除你上面创建的集群:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kind delete cluster --name psa-ns-level </span></span></code></pre></div><h2 id=接下来>接下来</h2><ul><li><p>运行一个 <a href=/examples/security/kind-with-namespace-level-baseline-pod-security.sh>shell 脚本</a> 一次执行所有前面的步骤。</p><ol><li>创建 kind 集群</li><li>创建新的名字空间</li><li>在 <code>enforce</code> 模式下应用 <code>baseline</code> Pod 安全标准, 同时在 <code>warn</code> 和 <code>audit</code> 模式下应用 <code>restricted</code> Pod 安全标准。</li><li>创建一个应用以下 Pod 安全标准的新 Pod</li></ol></li><li><p><a href=/zh-cn/docs/concepts/security/pod-security-admission/>Pod 安全准入</a></p></li><li><p><a href=/zh-cn/docs/concepts/security/pod-security-standards/>Pod 安全标准</a></p></li><li><p><a href=/zh-cn/docs/tutorials/security/cluster-level-pss/>在集群级别应用 Pod 安全标准</a></p></li></ul></div><div class=td-content style=page-break-before:always><h1 id=pg-fca078b8ac6b82352ed52187a2da91b7>4.3 - 使用 AppArmor 限制容器对资源的访问</h1><div class="feature-state-notice feature-stable" title="特性门控: AppArmor"><span class=feature-state-name>特性状态:</span> <code>Kubernetes v1.31 [stable]</code> (enabled by default: true)</div><p>本页面向你展示如何在节点上加载 AppArmor 配置文件并在 Pod 中强制应用这些配置文件。 要了解有关 Kubernetes 如何使用 AppArmor 限制 Pod 的更多信息,请参阅 <a href=/zh-cn/docs/concepts/security/linux-kernel-security-constraints/#apparmor>Pod 和容器的 Linux 内核安全约束</a>。</p><h2 id=教程目标>教程目标</h2><ul><li>查看如何在节点上加载配置文件示例</li><li>了解如何在 Pod 上强制执行配置文件</li><li>了解如何检查配置文件是否已加载</li><li>查看违反配置文件时会发生什么</li><li>查看无法加载配置文件时会发生什么</li></ul><h2 id=准备开始>准备开始</h2><p>AppArmor 是一个可选的内核模块和 Kubernetes 特性,因此请在继续之前验证你的节点是否支持它:</p><ol><li><p>AppArmor 内核模块已启用 —— 要使 Linux 内核强制执行 AppArmor 配置文件, 必须安装并且启动 AppArmor 内核模块。默认情况下,有几个发行版支持该模块, 如 Ubuntu 和 SUSE,还有许多发行版提供可选支持。要检查模块是否已启用,请检查 <code>/sys/module/apparmor/parameters/enabled</code> 文件:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>cat /sys/module/apparmor/parameters/enabled </span></span><span style=display:flex><span>Y </span></span></code></pre></div><p>kubelet 会先验证主机上是否已启用 AppArmor,然后再接纳显式配置了 AppArmor 的 Pod。</p></li></ol><ol start=2><li>容器运行时支持 AppArmor —— 所有常见的 Kubernetes 支持的容器运行时都应该支持 AppArmor, 包括 <a class=glossary-tooltip title='专用于 Kubernetes 的轻量级容器运行时软件' data-toggle=tooltip data-placement=top href=https://cri-o.io/#what-is-cri-o target=_blank aria-label=CRI-O>CRI-O</a> 和 <a class=glossary-tooltip title=强调简单性、健壮性和可移植性的一种容器运行时 data-toggle=tooltip data-placement=top href=https://containerd.io/docs/ target=_blank aria-label=containerd>containerd</a>。 请参考相应的运行时文档并验证集群是否满足使用 AppArmor 的要求。</li></ol><ol start=3><li><p>配置文件已加载 —— 通过指定每个容器应使用的 AppArmor 配置文件, AppArmor 会被应用到 Pod 上。如果所指定的配置文件未加载到内核, kubelet 将拒绝 Pod。 通过检查 <code>/sys/kernel/security/apparmor/profiles</code> 文件, 可以查看节点加载了哪些配置文件。例如:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>ssh gke-test-default-pool-239f5d02-gyn2 <span style=color:#b44>"sudo cat /sys/kernel/security/apparmor/profiles | sort"</span> </span></span></code></pre></div><pre tabindex=0><code>apparmor-test-deny-write (enforce) apparmor-test-audit-write (enforce) docker-default (enforce) k8s-nginx (enforce) </code></pre><p>有关在节点上加载配置文件的详细信息,请参见<a href=#setting-up-nodes-with-profiles>使用配置文件设置节点</a>。</p></li></ol><h2 id=securing-a-pod>保护 Pod</h2><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>在 Kubernetes v1.30 之前,AppArmor 是通过注解指定的。 使用文档版本选择器查看包含此已弃用 API 的文档。</p></div><p>AppArmor 配置文件可以在 Pod 级别或容器级别指定。容器 AppArmor 配置文件优先于 Pod 配置文件。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>securityContext</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>appArmorProfile</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>type</span>:<span style=color:#bbb> </span><profile_type><span style=color:#bbb> </span></span></span></code></pre></div><p>其中 <code><profile_type></code> 是以下之一:</p><ul><li><code>RuntimeDefault</code> 使用运行时的默认配置文件</li><li><code>Localhost</code> 使用主机上加载的配置文件(见下文)</li><li><code>Unconfined</code> 无需 AppArmor 即可运行</li></ul><p>有关 AppArmor 配置文件 API 的完整详细信息,请参阅<a href=#specifying-apparmor-confinement>指定 AppArmor 限制</a>。</p><p>要验证是否应用了配置文件, 你可以通过检查容器根进程的进程属性来检查该进程是否正在使用正确的配置文件运行:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl <span style=color:#a2f>exec</span> <pod_name> -- cat /proc/1/attr/current </span></span></code></pre></div><p>输出应如下所示:</p><pre tabindex=0><code>cri-containerd.apparmor.d (enforce) </code></pre><p>你还可以通过检查容器的 proc attr,直接验证容器的根进程是否以正确的配置文件运行:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl <span style=color:#a2f>exec</span> <pod_name> -- cat /proc/1/attr/current </span></span></code></pre></div><pre tabindex=0><code>k8s-apparmor-example-deny-write (enforce) </code></pre><h2 id=example>举例</h2><p><strong>本例假设你已经设置了一个集群使用 AppArmor 支持。</strong></p><p>首先,将要使用的配置文件加载到节点上,该配置文件阻止所有文件写入操作:</p><pre tabindex=0><code>#include <tunables/global> profile k8s-apparmor-example-deny-write flags=(attach_disconnected) { #include <abstractions/base> file, # 拒绝所有文件写入 deny /** w, } </code></pre><p>由于不知道 Pod 将被调度到哪里,该配置文件需要加载到所有节点上。 在本例中,你可以使用 SSH 来安装配置文件, 但是在<a href=#setting-up-nodes-with-profiles>使用配置文件设置节点</a>中讨论了其他方法。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 此示例假设节点名称与主机名称匹配,并且可通过 SSH 访问。</span> </span></span><span style=display:flex><span><span style=color:#b8860b>NODES</span><span style=color:#666>=(</span><span style=color:#a2f;font-weight:700>$(</span>kubectl get nodes -o name<span style=color:#a2f;font-weight:700>)</span><span style=color:#666>)</span> </span></span><span style=display:flex><span><span style=color:#a2f;font-weight:700>for</span> NODE in <span style=color:#b68;font-weight:700>${</span><span style=color:#b8860b>NODES</span>[*]<span style=color:#b68;font-weight:700>}</span>; <span style=color:#a2f;font-weight:700>do</span> ssh <span style=color:#b8860b>$NODE</span> <span style=color:#b44>'sudo apparmor_parser -q <<EOF </span></span></span><span style=display:flex><span><span style=color:#b44>#include <tunables/global> </span></span></span><span style=display:flex><span><span style=color:#b44> </span></span></span><span style=display:flex><span><span style=color:#b44>profile k8s-apparmor-example-deny-write flags=(attach_disconnected) { </span></span></span><span style=display:flex><span><span style=color:#b44> #include <abstractions/base> </span></span></span><span style=display:flex><span><span style=color:#b44> </span></span></span><span style=display:flex><span><span style=color:#b44> file, </span></span></span><span style=display:flex><span><span style=color:#b44> </span></span></span><span style=display:flex><span><span style=color:#b44> # Deny all file writes. </span></span></span><span style=display:flex><span><span style=color:#b44> deny /** w, </span></span></span><span style=display:flex><span><span style=color:#b44>} </span></span></span><span style=display:flex><span><span style=color:#b44>EOF'</span> </span></span><span style=display:flex><span><span style=color:#a2f;font-weight:700>done</span> </span></span></code></pre></div><p>接下来,运行一个带有拒绝写入配置文件的简单 “Hello AppArmor” Pod:</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/pods/security/hello-apparmor.yaml download=pods/security/hello-apparmor.yaml><code>pods/security/hello-apparmor.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("pods-security-hello-apparmor-yaml")' title="复制 pods/security/hello-apparmor.yaml 到剪贴板"></img></div><div class=includecode id=pods-security-hello-apparmor-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Pod<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>hello-apparmor<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>securityContext</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>appArmorProfile</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>type</span>:<span style=color:#bbb> </span>Localhost<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>localhostProfile</span>:<span style=color:#bbb> </span>k8s-apparmor-example-deny-write<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>hello<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>busybox:1.28<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>command</span>:<span style=color:#bbb> </span>[<span style=color:#bbb> </span><span style=color:#b44>"sh"</span>,<span style=color:#bbb> </span><span style=color:#b44>"-c"</span>,<span style=color:#bbb> </span><span style=color:#b44>"echo 'Hello AppArmor!' && sleep 1h"</span><span style=color:#bbb> </span>]<span style=color:#bbb> </span></span></span></code></pre></div></div></div><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl create -f hello-apparmor.yaml </span></span></code></pre></div><p>你可以通过检查其 <code>/proc/1/attr/current</code> 来验证容器是否确实使用该配置文件运行:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl <span style=color:#a2f>exec</span> hello-apparmor -- cat /proc/1/attr/current </span></span></code></pre></div><p>输出应该是:</p><pre tabindex=0><code>k8s-apparmor-example-deny-write (enforce) </code></pre><p>最后,你可以看到,如果你通过写入文件来违反配置文件会发生什么:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl <span style=color:#a2f>exec</span> hello-apparmor -- touch /tmp/test </span></span></code></pre></div><pre tabindex=0><code>touch: /tmp/test: Permission denied error: error executing remote command: command terminated with non-zero exit code: Error executing in Docker Container: 1 </code></pre><p>最后,看看如果你尝试指定尚未加载的配置文件会发生什么:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl create -f /dev/stdin <span style=color:#b44><<EOF </span></span></span><span style=display:flex><span><span style=color:#b44>apiVersion: v1 </span></span></span><span style=display:flex><span><span style=color:#b44>kind: Pod </span></span></span><span style=display:flex><span><span style=color:#b44>metadata: </span></span></span><span style=display:flex><span><span style=color:#b44> name: hello-apparmor-2 </span></span></span><span style=display:flex><span><span style=color:#b44>spec: </span></span></span><span style=display:flex><span><span style=color:#b44> securityContext: </span></span></span><span style=display:flex><span><span style=color:#b44> appArmorProfile: </span></span></span><span style=display:flex><span><span style=color:#b44> type: Localhost </span></span></span><span style=display:flex><span><span style=color:#b44> localhostProfile: k8s-apparmor-example-allow-write </span></span></span><span style=display:flex><span><span style=color:#b44> containers: </span></span></span><span style=display:flex><span><span style=color:#b44> - name: hello </span></span></span><span style=display:flex><span><span style=color:#b44> image: busybox:1.28 </span></span></span><span style=display:flex><span><span style=color:#b44> command: [ "sh", "-c", "echo 'Hello AppArmor!' && sleep 1h" ] </span></span></span><span style=display:flex><span><span style=color:#b44>EOF</span> </span></span></code></pre></div><pre tabindex=0><code>pod/hello-apparmor-2 created </code></pre><p>虽然 Pod 创建成功,但进一步检查会发现它陷入 pending 状态:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl describe pod hello-apparmor-2 </span></span></code></pre></div><pre tabindex=0><code>Name: hello-apparmor-2 Namespace: default Node: gke-test-default-pool-239f5d02-x1kf/10.128.0.27 Start Time: Tue, 30 Aug 2016 17:58:56 -0700 Labels: <none> Annotations: container.apparmor.security.beta.kubernetes.io/hello=localhost/k8s-apparmor-example-allow-write Status: Pending ... Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 10s default-scheduler Successfully assigned default/hello-apparmor to gke-test-default-pool-239f5d02-x1kf Normal Pulled 8s kubelet Successfully pulled image "busybox:1.28" in 370.157088ms (370.172701ms including waiting) Normal Pulling 7s (x2 over 9s) kubelet Pulling image "busybox:1.28" Warning Failed 7s (x2 over 8s) kubelet Error: failed to get container spec opts: failed to generate apparmor spec opts: apparmor profile not found k8s-apparmor-example-allow-write Normal Pulled 7s kubelet Successfully pulled image "busybox:1.28" in 90.980331ms (91.005869ms including waiting) </code></pre><p>事件提供错误消息及其原因,具体措辞与运行时相关:</p><pre tabindex=0><code> Warning Failed 7s (x2 over 8s) kubelet Error: failed to get container spec opts: failed to generate apparmor spec opts: apparmor profile not found </code></pre><h2 id=administration>管理</h2><h3 id=setting-up-nodes-with-profiles>使用配置文件设置节点</h3><p>Kubernetes 1.31 目前不提供任何本地机制来将 AppArmor 配置文件加载到节点上。 可以通过自定义基础设施或工具(例如 <a href=https://github.com/kubernetes-sigs/security-profiles-operator>Kubernetes Security Profiles Operator</a>) 加载配置文件。</p><p>调度程序不知道哪些配置文件加载到哪个节点上,因此必须将全套配置文件加载到每个节点上。 另一种方法是为节点上的每个配置文件(或配置文件类)添加节点标签, 并使用<a href=/zh-cn/docs/concepts/scheduling-eviction/assign-pod-node/>节点选择器</a>确保 Pod 在具有所需配置文件的节点上运行。</p><h2 id=authoring-profiles>编写配置文件</h2><p>获得正确指定的 AppArmor 配置文件可能是一件棘手的事情。幸运的是,有一些工具可以帮助你做到这一点:</p><ul><li><code>aa-genprof</code> 和 <code>aa-logprof</code> 通过监视应用程序的活动和日志并准许它所执行的操作来生成配置文件规则。 <a href=https://gitlab.com/apparmor/apparmor/wikis/Profiling_with_tools>AppArmor 文档</a>提供了进一步的指导。</li><li><a href=https://github.com/jfrazelle/bane>bane</a> 是一个用于 Docker的 AppArmor 配置文件生成器,它使用一种简化的画像语言(profile language)。</li></ul><p>想要调试 AppArmor 的问题,你可以检查系统日志,查看具体拒绝了什么。 AppArmor 将详细消息记录到 <code>dmesg</code>, 错误通常可以在系统日志中或通过 <code>journalctl</code> 找到。 更多详细信息参见 <a href=https://gitlab.com/apparmor/apparmor/wikis/AppArmor_Failures>AppArmor 失败</a>。</p><h2 id=specifying-apparmor-confinement>指定 AppArmor 限制</h2><div class="alert alert-caution" role=alert><h4 class=alert-heading>注意:</h4><p>在 Kubernetes v1.30 之前,AppArmor 是通过注解指定的。使用文档版本选择器查看包含此已弃用 API 的文档。</p></div><h3 id=appArmorProfile>安全上下文中的 AppArmor 配置文件</h3><p>你可以在容器的 <code>securityContext</code> 或 Pod 的 <code>securityContext</code> 中设置 <code>appArmorProfile</code>。 如果在 Pod 级别设置配置文件,该配置将被用作 Pod 中所有容器(包括 Init、Sidecar 和临时容器)的默认配置文件。 如果同时设置了 Pod 和容器 AppArmor 配置文件,则将使用容器的配置文件。</p><p>AppArmor 配置文件有 2 个字段:</p><p><code>type</code> <strong>(必需)</strong> - 指示将应用哪种 AppArmor 配置文件。有效选项是:</p><dl><dt><code>Localhost</code></dt><dd>节点上预加载的配置文件(由 <code>localhostProfile</code> 指定)。</dd><dt><code>RuntimeDefault</code></dt><dd>容器运行时的默认配置文件。</dd><dt><code>Unconfined</code></dt><dd>不强制执行 AppArmor。</dd></dl><p><code>localhostProfile</code> - 在节点上加载的、应被使用的配置文件的名称。 该配置文件必须在节点上预先配置才能工作。 当且仅当 <code>type</code> 是 <code>Localhost</code> 时,必须提供此选项。</p><h2 id=接下来>接下来</h2><p>其他资源:</p><ul><li><a href=https://gitlab.com/apparmor/apparmor/wikis/QuickProfileLanguage>Apparmor 配置文件语言快速指南</a></li><li><a href=https://gitlab.com/apparmor/apparmor/wikis/Policy_Layout>Apparmor 核心策略参考</a></li></ul></div><div class=td-content style=page-break-before:always><h1 id=pg-8b105172a11322c70d0223bc9dff1904>4.4 - 使用 seccomp 限制容器的系统调用</h1><div class="feature-state-notice feature-stable"><span class=feature-state-name>特性状态:</span> <code>Kubernetes v1.19 [stable]</code></div><p>Seccomp 代表安全计算(Secure Computing)模式,自 2.6.12 版本以来,一直是 Linux 内核的一个特性。 它可以用来沙箱化进程的权限,限制进程从用户态到内核态的调用。 Kubernetes 能使你自动将加载到<a class=glossary-tooltip title='Kubernetes 中的工作机器称作节点。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/architecture/nodes/ target=_blank aria-label=节点>节点</a>上的 seccomp 配置文件应用到你的 Pod 和容器。</p><p>识别你的工作负载所需要的权限是很困难的。在本篇教程中, 你将了解如何将 seccomp 配置文件加载到本地的 Kubernetes 集群中, 如何将它们应用到 Pod,以及如何开始制作只为容器进程提供必要的权限的配置文件。</p><h2 id=教程目标>教程目标</h2><ul><li>了解如何在节点上加载 seccomp 配置文件</li><li>了解如何将 seccomp 配置文件应用到容器上</li><li>观察容器进程对系统调用的审计</li><li>观察指定的配置文件缺失时的行为</li><li>观察违反 seccomp 配置文件的行为</li><li>了解如何创建细粒度的 seccomp 配置文件</li><li>了解如何应用容器运行时所默认的 seccomp 配置文件</li></ul><h2 id=准备开始>准备开始</h2><p>为了完成本篇教程中的所有步骤,你必须安装 <a href=/zh-cn/docs/tasks/tools/#kind>kind</a> 和 <a href=/zh-cn/docs/tasks/tools/#kubectl>kubectl</a>。</p><p>本教程中使用的命令假设你使用 <a href=https://www.docker.com/>Docker</a> 作为容器运行时。 (<code>kind</code> 创建的集群可以在内部使用不同的容器运行时)。 你也可以使用 <a href=https://podman.io/>Podman</a>,但如果使用了 Podman, 你必须执行特定的<a href=https://kind.sigs.k8s.io/docs/user/rootless/>指令</a>才能顺利完成任务。</p><p>本篇教程演示的某些示例仍然是 Beta 状态(自 v1.25 起),另一些示例则仅使用 seccomp 正式发布的功能。 你应该确保,针对你使用的版本, <a href=https://kind.sigs.k8s.io/docs/user/quick-start/#setting-kubernetes-version>正确配置</a>了集群。</p><p>本篇教程也使用了 <code>curl</code> 工具来下载示例到你的计算机上。 你可以使用其他自己偏好的工具来自适应这些步骤。</p><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>无法将 seccomp 配置文件应用于在容器的 <code>securityContext</code> 中设置了 <code>privileged: true</code> 的容器。 特权容器始终以 <code>Unconfined</code> 的方式运行。</p></div><h2 id=download-profiles>下载示例 seccomp 配置文件</h2><p>这些配置文件的内容将在稍后进行分析, 现在先将它们下载到名为 <code>profiles/</code> 的目录中,以便将它们加载到集群中。</p><ul class="nav nav-tabs" id=tab-with-code role=tablist><li class=nav-item><a data-toggle=tab class="nav-link active" href=#tab-with-code-0 role=tab aria-controls=tab-with-code-0 aria-selected=true>audit.json</a></li><li class=nav-item><a data-toggle=tab class=nav-link href=#tab-with-code-1 role=tab aria-controls=tab-with-code-1>violation.json</a></li><li class=nav-item><a data-toggle=tab class=nav-link href=#tab-with-code-2 role=tab aria-controls=tab-with-code-2>fine-grained.json</a></li></ul><div class=tab-content id=tab-with-code><div id=tab-with-code-0 class="tab-pane show active" role=tabpanel aria-labelledby=tab-with-code-0><p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/pods/security/seccomp/profiles/audit.json download=pods/security/seccomp/profiles/audit.json><code>pods/security/seccomp/profiles/audit.json</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("pods-security-seccomp-profiles-audit-json")' title="复制 pods/security/seccomp/profiles/audit.json 到剪贴板"></img></div><div class=includecode id=pods-security-seccomp-profiles-audit-json><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-json data-lang=json><span style=display:flex><span>{ </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"defaultAction"</span>: <span style=color:#b44>"SCMP_ACT_LOG"</span> </span></span><span style=display:flex><span>}</span></span></code></pre></div></div></div></div><div id=tab-with-code-1 class=tab-pane role=tabpanel aria-labelledby=tab-with-code-1><p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/pods/security/seccomp/profiles/violation.json download=pods/security/seccomp/profiles/violation.json><code>pods/security/seccomp/profiles/violation.json</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("pods-security-seccomp-profiles-violation-json")' title="复制 pods/security/seccomp/profiles/violation.json 到剪贴板"></img></div><div class=includecode id=pods-security-seccomp-profiles-violation-json><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-json data-lang=json><span style=display:flex><span>{ </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"defaultAction"</span>: <span style=color:#b44>"SCMP_ACT_ERRNO"</span> </span></span><span style=display:flex><span>}</span></span></code></pre></div></div></div></div><div id=tab-with-code-2 class=tab-pane role=tabpanel aria-labelledby=tab-with-code-2><p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/pods/security/seccomp/profiles/fine-grained.json download=pods/security/seccomp/profiles/fine-grained.json><code>pods/security/seccomp/profiles/fine-grained.json</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("pods-security-seccomp-profiles-fine-grained-json")' title="复制 pods/security/seccomp/profiles/fine-grained.json 到剪贴板"></img></div><div class=includecode id=pods-security-seccomp-profiles-fine-grained-json><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-json data-lang=json><span style=display:flex><span>{ </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"defaultAction"</span>: <span style=color:#b44>"SCMP_ACT_ERRNO"</span>, </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"architectures"</span>: [ </span></span><span style=display:flex><span> <span style=color:#b44>"SCMP_ARCH_X86_64"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"SCMP_ARCH_X86"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"SCMP_ARCH_X32"</span> </span></span><span style=display:flex><span> ], </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"syscalls"</span>: [ </span></span><span style=display:flex><span> { </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"names"</span>: [ </span></span><span style=display:flex><span> <span style=color:#b44>"accept4"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"epoll_wait"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"pselect6"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"futex"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"madvise"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"epoll_ctl"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"getsockname"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"setsockopt"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"vfork"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"mmap"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"read"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"write"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"close"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"arch_prctl"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"sched_getaffinity"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"munmap"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"brk"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"rt_sigaction"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"rt_sigprocmask"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"sigaltstack"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"gettid"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"clone"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"bind"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"socket"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"openat"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"readlinkat"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"exit_group"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"epoll_create1"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"listen"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"rt_sigreturn"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"sched_yield"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"clock_gettime"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"connect"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"dup2"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"epoll_pwait"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"execve"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"exit"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"fcntl"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"getpid"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"getuid"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"ioctl"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"mprotect"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"nanosleep"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"open"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"poll"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"recvfrom"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"sendto"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"set_tid_address"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"setitimer"</span>, </span></span><span style=display:flex><span> <span style=color:#b44>"writev"</span> </span></span><span style=display:flex><span> ], </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"action"</span>: <span style=color:#b44>"SCMP_ACT_ALLOW"</span> </span></span><span style=display:flex><span> } </span></span><span style=display:flex><span> ] </span></span><span style=display:flex><span>}</span></span></code></pre></div></div></div></div></div><p>执行这些命令:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>mkdir ./profiles </span></span><span style=display:flex><span>curl -L -o profiles/audit.json https://k8s.io/examples/pods/security/seccomp/profiles/audit.json </span></span><span style=display:flex><span>curl -L -o profiles/violation.json https://k8s.io/examples/pods/security/seccomp/profiles/violation.json </span></span><span style=display:flex><span>curl -L -o profiles/fine-grained.json https://k8s.io/examples/pods/security/seccomp/profiles/fine-grained.json </span></span><span style=display:flex><span>ls profiles </span></span></code></pre></div><p>你应该看到在最后一步的末尾列出有三个配置文件:</p><pre tabindex=0><code>audit.json fine-grained.json violation.json </code></pre><h2 id=create-a-local-kubernetes-cluster-with-kind>使用 kind 创建本地 Kubernetes 集群</h2><p>为简单起见,<a href=https://kind.sigs.k8s.io/>kind</a> 可用来创建加载了 seccomp 配置文件的单节点集群。 Kind 在 Docker 中运行 Kubernetes,因此集群的每个节点都是一个容器。 这允许将文件挂载到每个容器的文件系统中,类似于将文件加载到节点上。</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/pods/security/seccomp/kind.yaml download=pods/security/seccomp/kind.yaml><code>pods/security/seccomp/kind.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("pods-security-seccomp-kind-yaml")' title="复制 pods/security/seccomp/kind.yaml 到剪贴板"></img></div><div class=includecode id=pods-security-seccomp-kind-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>kind.x-k8s.io/v1alpha4<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Cluster<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>nodes</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span>- <span style=color:green;font-weight:700>role</span>:<span style=color:#bbb> </span>control-plane<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>extraMounts</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>hostPath</span>:<span style=color:#bbb> </span><span style=color:#b44>"./profiles"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containerPath</span>:<span style=color:#bbb> </span><span style=color:#b44>"/var/lib/kubelet/seccomp/profiles"</span></span></span></code></pre></div></div></div><p>下载该示例 kind 配置,并将其保存到名为 <code>kind.yaml</code> 的文件中:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>curl -L -O https://k8s.io/examples/pods/security/seccomp/kind.yaml </span></span></code></pre></div><p>你可以通过设置节点的容器镜像来设置特定的 Kubernetes 版本。 有关此类配置的更多信息, 参阅 kind 文档中<a href=https://kind.sigs.k8s.io/docs/user/configuration/#nodes>节点</a>小节。 本篇教程假定你正在使用 Kubernetes v1.31。</p><p>作为 Beta 特性,你可以将 Kubernetes 配置为使用<a class=glossary-tooltip title=容器运行时是负责运行容器的软件。 data-toggle=tooltip data-placement=top href=/zh-cn/docs/setup/production-environment/container-runtimes target=_blank aria-label=容器运行时>容器运行时</a>默认首选的配置文件, 而不是回退到 <code>Unconfined</code>。 如果你想尝试,请在继续之前参阅 <a href=#enable-runtimedefault-as-default>启用使用 <code>RuntimeDefault</code> 作为所有工作负载的默认 seccomp 配置文件</a>。</p><p>有了 kind 配置后,使用该配置创建 kind 集群:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kind create cluster --config<span style=color:#666>=</span>kind.yaml </span></span></code></pre></div><p>新的 Kubernetes 集群准备就绪后,找出作为单节点集群运行的 Docker 容器:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>docker ps </span></span></code></pre></div><p>你应该看到输出中名为 <code>kind-control-plane</code> 的容器正在运行。 输出类似于:</p><pre tabindex=0><code>CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 6a96207fed4b kindest/node:v1.18.2 "/usr/local/bin/entr…" 27 seconds ago Up 24 seconds 127.0.0.1:42223->6443/tcp kind-control-plane </code></pre><p>如果观察该容器的文件系统, 你应该会看到 <code>profiles/</code> 目录已成功加载到 kubelet 的默认 seccomp 路径中。 使用 <code>docker exec</code> 在 Pod 中运行命令:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 将 6a96207fed4b 更改为你从 “docker ps” 看到的容器 ID</span> </span></span><span style=display:flex><span>docker <span style=color:#a2f>exec</span> -it 6a96207fed4b ls /var/lib/kubelet/seccomp/profiles </span></span></code></pre></div><pre tabindex=0><code>audit.json fine-grained.json violation.json </code></pre><p>你已验证这些 seccomp 配置文件可用于在 kind 中运行的 kubelet。</p><h2 id=create-pod-that-uses-the-container-runtime-default-seccomp-profile>创建使用容器运行时默认 seccomp 配置文件的 Pod</h2><p>大多数容器运行时都提供了一组合理的、默认被允许或默认被禁止的系统调用。 你可以通过将 Pod 或容器的安全上下文中的 seccomp 类型设置为 <code>RuntimeDefault</code> 来为你的工作负载采用这些默认值。</p><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>如果你已经启用了 <code>seccompDefault</code> <a href=/zh-cn/docs/reference/config-api/kubelet-config.v1beta1/>配置</a>, 只要没有指定其他 seccomp 配置文件,那么 Pod 就会使用 <code>RuntimeDefault</code> seccomp 配置文件。 否则,默认值为 <code>Unconfined</code>。</p></div><p>这是一个 Pod 的清单,它要求其所有容器使用 <code>RuntimeDefault</code> seccomp 配置文件:</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/pods/security/seccomp/ga/default-pod.yaml download=pods/security/seccomp/ga/default-pod.yaml><code>pods/security/seccomp/ga/default-pod.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("pods-security-seccomp-ga-default-pod-yaml")' title="复制 pods/security/seccomp/ga/default-pod.yaml 到剪贴板"></img></div><div class=includecode id=pods-security-seccomp-ga-default-pod-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Pod<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>default-pod<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>default-pod<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>securityContext</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>seccompProfile</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>type</span>:<span style=color:#bbb> </span>RuntimeDefault<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>test-container<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>hashicorp/http-echo:1.0<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>args</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:#b44>"-text=just made some more syscalls!"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>securityContext</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>allowPrivilegeEscalation</span>:<span style=color:#bbb> </span><span style=color:#a2f;font-weight:700>false</span></span></span></code></pre></div></div></div><p>创建此 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/default-pod.yaml </span></span></code></pre></div><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pod default-pod </span></span></code></pre></div><p>此 Pod 应该显示为已成功启动:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE default-pod 1/1 Running 0 20s </code></pre><p>在进入下一节之前先删除 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete pod default-pod --wait --now </span></span></code></pre></div><h2 id=create-a-pod-with-a-seccomp-profile-for-syscall-auditing>使用 seccomp 配置文件创建 Pod 以进行系统调用审计</h2><p>首先,将 <code>audit.json</code> 配置文件应用到新的 Pod 上,该配置文件将记录进程的所有系统调用。</p><p>这是该 Pod 的清单:</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/pods/security/seccomp/ga/audit-pod.yaml download=pods/security/seccomp/ga/audit-pod.yaml><code>pods/security/seccomp/ga/audit-pod.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("pods-security-seccomp-ga-audit-pod-yaml")' title="复制 pods/security/seccomp/ga/audit-pod.yaml 到剪贴板"></img></div><div class=includecode id=pods-security-seccomp-ga-audit-pod-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Pod<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>audit-pod<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>audit-pod<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>securityContext</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>seccompProfile</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>type</span>:<span style=color:#bbb> </span>Localhost<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>localhostProfile</span>:<span style=color:#bbb> </span>profiles/audit.json<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>test-container<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>hashicorp/http-echo:1.0<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>args</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:#b44>"-text=just made some syscalls!"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>securityContext</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>allowPrivilegeEscalation</span>:<span style=color:#bbb> </span><span style=color:#a2f;font-weight:700>false</span></span></span></code></pre></div></div></div><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>旧版本的 Kubernetes 允许你使用<a class=glossary-tooltip title=注解是以键值对的形式给资源对象附加随机的无法标识的元数据。 data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/overview/working-with-objects/annotations/ target=_blank aria-label=注解>注解</a>配置 seccomp 行为。Kubernetes 1.31 仅支持使用位于 <code>.spec.securityContext</code> 内的字段来配置 seccomp。本教程将阐述这个方法。</p></div><p>在集群中创建 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/audit-pod.yaml </span></span></code></pre></div><p>此配置文件不限制任何系统调用,因此 Pod 应该成功启动。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pod audit-pod </span></span></code></pre></div><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE audit-pod 1/1 Running 0 30s </code></pre><p>为了能够与容器暴露的端点交互, 创建一个 NodePort 类型的 <a class=glossary-tooltip title='将运行在一组 Pods 上的应用程序公开为网络服务的抽象方法。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/services-networking/service/ target=_blank aria-label=Service>Service</a>, 允许从 kind 控制平面容器内部访问端点。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl expose pod audit-pod --type NodePort --port <span style=color:#666>5678</span> </span></span></code></pre></div><p>检查 Service 在节点上分配的端口。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get service audit-pod </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE audit-pod NodePort 10.111.36.142 <none> 5678:32373/TCP 72s </code></pre><p>现在,你可以使用 <code>curl</code> 从 kind 控制平面容器内部访问该端点,位于该服务所公开的端口上。 使用 <code>docker exec</code> 在属于该控制平面容器的容器中运行 <code>curl</code> 命令:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 将 6a96207fed4b 更改为你从 “docker ps” 看到的控制平面容器 ID 和端口号 32373</span> </span></span><span style=display:flex><span>docker <span style=color:#a2f>exec</span> -it 6a96207fed4b curl localhost:32373 </span></span></code></pre></div><pre tabindex=0><code>just made some syscalls! </code></pre><p>你可以看到该进程正在运行,但它实际上进行了哪些系统调用? 因为这个 Pod 在本地集群中运行,你应该能够在本地系统的 <code>/var/log/syslog</code> 中看到它们。 打开一个新的终端窗口并 <code>tail</code> 来自 <code>http-echo</code> 的调用的输出:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 在你的计算机上,日志路径可能不是 "/var/log/syslog"</span> </span></span><span style=display:flex><span>tail -f /var/log/syslog | grep <span style=color:#b44>'http-echo'</span> </span></span></code></pre></div><p>你应该已经看到了一些由 <code>http-echo</code> 进行的系统调用的日志, 如果你在控制平面容器中再次运行 <code>curl</code>,你会看到更多的输出被写入到日志。</p><p>例如:</p><pre tabindex=0><code>Jul 6 15:37:40 my-machine kernel: [369128.669452] audit: type=1326 audit(1594067860.484:14536): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=51 compat=0 ip=0x46fe1f code=0x7ffc0000 Jul 6 15:37:40 my-machine kernel: [369128.669453] audit: type=1326 audit(1594067860.484:14537): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=54 compat=0 ip=0x46fdba code=0x7ffc0000 Jul 6 15:37:40 my-machine kernel: [369128.669455] audit: type=1326 audit(1594067860.484:14538): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=202 compat=0 ip=0x455e53 code=0x7ffc0000 Jul 6 15:37:40 my-machine kernel: [369128.669456] audit: type=1326 audit(1594067860.484:14539): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=288 compat=0 ip=0x46fdba code=0x7ffc0000 Jul 6 15:37:40 my-machine kernel: [369128.669517] audit: type=1326 audit(1594067860.484:14540): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=0 compat=0 ip=0x46fd44 code=0x7ffc0000 Jul 6 15:37:40 my-machine kernel: [369128.669519] audit: type=1326 audit(1594067860.484:14541): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=270 compat=0 ip=0x4559b1 code=0x7ffc0000 Jul 6 15:38:40 my-machine kernel: [369188.671648] audit: type=1326 audit(1594067920.488:14559): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=270 compat=0 ip=0x4559b1 code=0x7ffc0000 Jul 6 15:38:40 my-machine kernel: [369188.671726] audit: type=1326 audit(1594067920.488:14560): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=202 compat=0 ip=0x455e53 code=0x7ffc0000 </code></pre><p>通过查看每一行的 <code>syscall=</code> 条目,你可以开始了解 <code>http-echo</code> 进程所需的系统调用。 虽然这些不太可能包含它使用的所有系统调用,但它可以作为此容器的 seccomp 配置文件的基础。</p><p>在转到下一节之前删除该 Service 和 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete service audit-pod --wait </span></span><span style=display:flex><span>kubectl delete pod audit-pod --wait --now </span></span></code></pre></div><h2 id=create-pod-with-a-seccomp-profile-that-causes-violation>使用导致违规的 seccomp 配置文件创建 Pod</h2><p>出于演示目的,将配置文件应用于不允许任何系统调用的 Pod 上。</p><p>此演示的清单是:</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/pods/security/seccomp/ga/violation-pod.yaml download=pods/security/seccomp/ga/violation-pod.yaml><code>pods/security/seccomp/ga/violation-pod.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("pods-security-seccomp-ga-violation-pod-yaml")' title="复制 pods/security/seccomp/ga/violation-pod.yaml 到剪贴板"></img></div><div class=includecode id=pods-security-seccomp-ga-violation-pod-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Pod<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>violation-pod<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>violation-pod<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>securityContext</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>seccompProfile</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>type</span>:<span style=color:#bbb> </span>Localhost<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>localhostProfile</span>:<span style=color:#bbb> </span>profiles/violation.json<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>test-container<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>hashicorp/http-echo:1.0<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>args</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:#b44>"-text=just made some syscalls!"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>securityContext</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>allowPrivilegeEscalation</span>:<span style=color:#bbb> </span><span style=color:#a2f;font-weight:700>false</span></span></span></code></pre></div></div></div><p>尝试在集群中创建 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/violation-pod.yaml </span></span></code></pre></div><p>Pod 已创建,但存在问题。 如果你检查 Pod 状态,你应该看到它没有启动。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pod violation-pod </span></span></code></pre></div><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE violation-pod 0/1 CrashLoopBackOff 1 6s </code></pre><p>如上例所示,<code>http-echo</code> 进程需要相当多的系统调用。 这里 seccomp 已通过设置 <code>"defaultAction": "SCMP_ACT_ERRNO"</code> 被指示为在发生任何系统调用时报错。 这是非常安全的,但消除了做任何有意义的事情的能力。 你真正想要的是只给工作负载它们所需要的权限。</p><p>在进入下一节之前删除该 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete pod violation-pod --wait --now </span></span></code></pre></div><h2 id=create-pod-with-a-seccomp-profile-that-only-allows-necessary-syscalls>使用只允许必要的系统调用的 seccomp 配置文件创建 Pod</h2><p>如果你看一看 <code>fine-grained.json</code> 配置文件, 你会注意到第一个示例的 syslog 中看到的一些系统调用, 其中配置文件设置为 <code>"defaultAction": "SCMP_ACT_LOG"</code>。 现在的配置文件设置 <code>"defaultAction": "SCMP_ACT_ERRNO"</code>, 但在 <code>"action": "SCMP_ACT_ALLOW"</code> 块中明确允许一组系统调用。 理想情况下,容器将成功运行,并且你看到没有消息发送到 <code>syslog</code>。</p><p>此示例的清单是:</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/pods/security/seccomp/ga/fine-pod.yaml download=pods/security/seccomp/ga/fine-pod.yaml><code>pods/security/seccomp/ga/fine-pod.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("pods-security-seccomp-ga-fine-pod-yaml")' title="复制 pods/security/seccomp/ga/fine-pod.yaml 到剪贴板"></img></div><div class=includecode id=pods-security-seccomp-ga-fine-pod-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Pod<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>fine-pod<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>fine-pod<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>securityContext</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>seccompProfile</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>type</span>:<span style=color:#bbb> </span>Localhost<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>localhostProfile</span>:<span style=color:#bbb> </span>profiles/fine-grained.json<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>test-container<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>hashicorp/http-echo:1.0<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>args</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:#b44>"-text=just made some syscalls!"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>securityContext</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>allowPrivilegeEscalation</span>:<span style=color:#bbb> </span><span style=color:#a2f;font-weight:700>false</span></span></span></code></pre></div></div></div><p>在你的集群中创建 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/fine-pod.yaml </span></span></code></pre></div><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pod fine-pod </span></span></code></pre></div><p>此 Pod 应该显示为已成功启动:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE fine-pod 1/1 Running 0 30s </code></pre><p>打开一个新的终端窗口并使用 <code>tail</code> 来监视提到来自 <code>http-echo</code> 的调用的日志条目:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 你计算机上的日志路径可能与 “/var/log/syslog” 不同</span> </span></span><span style=display:flex><span>tail -f /var/log/syslog | grep <span style=color:#b44>'http-echo'</span> </span></span></code></pre></div><p>接着,使用 NodePort Service 公开 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl expose pod fine-pod --type NodePort --port <span style=color:#666>5678</span> </span></span></code></pre></div><p>检查节点上的 Service 分配了什么端口:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get service fine-pod </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE fine-pod NodePort 10.111.36.142 <none> 5678:32373/TCP 72s </code></pre><p>使用 <code>curl</code> 从 kind 控制平面容器内部访问端点:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 将 6a96207fed4b 更改为你从 “docker ps” 看到的控制平面容器 ID 和端口号 32373</span> </span></span><span style=display:flex><span>docker <span style=color:#a2f>exec</span> -it 6a96207fed4b curl localhost:32373 </span></span></code></pre></div><pre tabindex=0><code>just made some syscalls! </code></pre><p>你应该在 <code>syslog</code> 中看不到任何输出。 这是因为配置文件允许所有必要的系统调用,并指定如果调用列表之外的系统调用应发生错误。 从安全角度来看,这是一种理想的情况,但需要在分析程序时付出一些努力。 如果有一种简单的方法可以在不需要太多努力的情况下更接近这种安全性,那就太好了。</p><p>在进入下一节之前删除该 Service 和 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete service fine-pod --wait </span></span><span style=display:flex><span>kubectl delete pod fine-pod --wait --now </span></span></code></pre></div><h2 id=enable-runtimedefault-as-default>启用使用 <code>RuntimeDefault</code> 作为所有工作负载的默认 seccomp 配置文件</h2><div class="feature-state-notice feature-stable"><span class=feature-state-name>特性状态:</span> <code>Kubernetes v1.27 [stable]</code></div><p>要采用为 Seccomp(安全计算模式)设置默认配置文件这一行为,你必须使用在想要启用此行为的每个节点上启用 <code>--seccomp-default</code> <a href=/zh-cn/docs/reference/command-line-tools-reference/kubelet>命令行标志</a>来运行 kubelet。</p><p>如果启用,kubelet 将会默认使用 <code>RuntimeDefault</code> seccomp 配置文件, (这一配置文件是由容器运行时定义的),而不是使用 <code>Unconfined</code>(禁用 seccomp)模式。 默认的配置文件旨在提供一组限制性较强且能保留工作负载功能的安全默认值。 不同容器运行时及其不同发布版本之间的默认配置文件可能有所不同, 例如在比较来自 CRI-O 和 containerd 的配置文件时。</p><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>启用该功能既不会更改 Kubernetes <code>securityContext.seccompProfile</code> API 字段, 也不会添加已弃用的工作负载注解。 这样用户可以随时回滚,而且无需实际更改工作负载配置。 <a href=https://github.com/kubernetes-sigs/cri-tools><code>crictl inspect</code></a> 之类的工具可用于检查容器正在使用哪个 seccomp 配置文件。</p></div><p>与其他工作负载相比,某些工作负载可能需要更少的系统调用限制。 这意味着即使使用 <code>RuntimeDefault</code> 配置文件,它们也可能在运行时失败。 要应对此类故障,你可以:</p><ul><li>显式地以 <code>Unconfined</code> 模式运行工作负载。</li><li>禁用节点的 <code>SeccompDefault</code> 特性。同时,确保工作负载被调度到禁用该特性的节点上。</li><li>为工作负载创建自定义 seccomp 配置文件。</li></ul><p>如果你将此特性引入到类似的生产集群中, Kubernetes 项目建议你在部分节点上启用此特性门控, 然后在整个集群范围内推出更改之前,测试工作负载执行情况。</p><p>你可以在相关的 Kubernetes 增强提案(KEP) 中找到可能的升级和降级策略的更详细信息: <a href=https://github.com/kubernetes/enhancements/tree/9a124fd29d1f9ddf2ff455c49a630e3181992c25/keps/sig-node/2413-seccomp-by-default#upgrade--downgrade-strategy>默认启用 Seccomp</a>。</p><p>Kubernetes 1.31 允许你配置 Seccomp 配置文件, 当 Pod 的规约未定义特定的 Seccomp 配置文件时应用该配置文件。 但是,你仍然需要为合适的节点启用这种设置默认配置的能力。</p><p>如果你正在运行 Kubernetes 1.31 集群并希望启用该特性,请使用 <code>--seccomp-default</code> 命令行参数运行 kubelet, 或通过 <a href=/zh-cn/docs/tasks/administer-cluster/kubelet-config-file/>kubelet 配置文件</a>启用。</p><p>要在 <a href=https://kind.sigs.k8s.io>kind</a> 启用特性门控, 请确保 <code>kind</code> 提供所需的最低 Kubernetes 版本, 并<a href=https://kind.sigs.k8s.io/docs/user/quick-start/#enable-feature-gates-in-your-cluster>在 kind 配置中</a> 启用 <code>SeccompDefault</code> 特性:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Cluster<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>kind.x-k8s.io/v1alpha4<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>nodes</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>role</span>:<span style=color:#bbb> </span>control-plane<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>kindest/node:v1.28.0@sha256:9f3ff58f19dcf1a0611d11e8ac989fdb30a28f40f236f59f0bea31fb956ccf5c<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>kubeadmConfigPatches</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- |<span style=color:#b44;font-style:italic> </span></span></span><span style=display:flex><span><span style=color:#b44;font-style:italic> kind: JoinConfiguration </span></span></span><span style=display:flex><span><span style=color:#b44;font-style:italic> nodeRegistration: </span></span></span><span style=display:flex><span><span style=color:#b44;font-style:italic> kubeletExtraArgs: </span></span></span><span style=display:flex><span><span style=color:#b44;font-style:italic> seccomp-default: "true"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>role</span>:<span style=color:#bbb> </span>worker<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>kindest/node:v1.28.0@sha256:9f3ff58f19dcf1a0611d11e8ac989fdb30a28f40f236f59f0bea31fb956ccf5c<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>kubeadmConfigPatches</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- |<span style=color:#b44;font-style:italic> </span></span></span><span style=display:flex><span><span style=color:#b44;font-style:italic> kind: JoinConfiguration </span></span></span><span style=display:flex><span><span style=color:#b44;font-style:italic> nodeRegistration: </span></span></span><span style=display:flex><span><span style=color:#b44;font-style:italic> kubeletExtraArgs: </span></span></span><span style=display:flex><span><span style=color:#b44;font-style:italic> seccomp-default: "true"</span><span style=color:#bbb> </span></span></span></code></pre></div><p>如果集群已就绪,则运行一个 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl run --rm -it --restart<span style=color:#666>=</span>Never --image<span style=color:#666>=</span>alpine alpine -- sh </span></span></code></pre></div><p>现在默认的 seccomp 配置文件应该已经生效。 这可以通过使用 <code>docker exec</code> 为 kind 上的容器运行 <code>crictl inspect</code> 来验证:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>docker <span style=color:#a2f>exec</span> -it kind-worker bash -c <span style=color:#b62;font-weight:700>\ </span></span></span><span style=display:flex><span><span style=color:#b62;font-weight:700></span> <span style=color:#b44>'crictl inspect $(crictl ps --name=alpine -q) | jq .info.runtimeSpec.linux.seccomp'</span> </span></span></code></pre></div><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-json data-lang=json><span style=display:flex><span>{ </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"defaultAction"</span>: <span style=color:#b44>"SCMP_ACT_ERRNO"</span>, </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"architectures"</span>: [<span style=color:#b44>"SCMP_ARCH_X86_64"</span>, <span style=color:#b44>"SCMP_ARCH_X86"</span>, <span style=color:#b44>"SCMP_ARCH_X32"</span>], </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"syscalls"</span>: [ </span></span><span style=display:flex><span> { </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"names"</span>: [<span style=color:#b44>"..."</span>] </span></span><span style=display:flex><span> } </span></span><span style=display:flex><span> ] </span></span><span style=display:flex><span>} </span></span></code></pre></div><h2 id=接下来>接下来</h2><p>你可以了解有关 Linux seccomp 的更多信息:</p><ul><li><a href=https://lwn.net/Articles/656307/>seccomp 概述</a></li><li><a href=https://docs.docker.com/engine/security/seccomp/>Docker 的 Seccomp 安全配置文件</a></li></ul></div><div class=td-content style=page-break-before:always><h1 id=pg-1efbbc2c3015389f835b1661d5effb29>5 - 无状态的应用</h1></div><div class=td-content><h1 id=pg-62caf420877232190a7404b8d93c6724>5.1 - 公开外部 IP 地址以访问集群中的应用</h1><p>此页面显示如何创建公开外部 IP 地址的 Kubernetes 服务对象。</p><h2 id=准备开始>准备开始</h2><ul><li>安装 <a href=/zh-cn/docs/tasks/tools/>kubectl</a>。</li><li>使用 Google Kubernetes Engine 或 Amazon Web Services 等云供应商创建 Kubernetes 集群。 本教程创建了一个<a href=/zh-cn/docs/tasks/access-application-cluster/create-external-load-balancer/>外部负载均衡器</a>, 需要云供应商。</li><li>配置 <code>kubectl</code> 与 Kubernetes API 服务器通信。有关说明,请参阅云供应商文档。</li></ul><h2 id=教程目标>教程目标</h2><ul><li>运行 Hello World 应用的五个实例。</li><li>创建一个公开外部 IP 地址的 Service 对象。</li><li>使用 Service 对象访问正在运行的应用。</li></ul><h2 id=creating-a-service-for-an-app-running-in-five-pods>为在五个 Pod 中运行的应用创建服务</h2><ol><li><p>在集群中运行 Hello World 应用:</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/service/load-balancer-example.yaml download=service/load-balancer-example.yaml><code>service/load-balancer-example.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("service-load-balancer-example-yaml")' title="复制 service/load-balancer-example.yaml 到剪贴板"></img></div><div class=includecode id=service-load-balancer-example-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>apps/v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Deployment<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app.kubernetes.io/name</span>:<span style=color:#bbb> </span>load-balancer-example<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>hello-world<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>replicas</span>:<span style=color:#bbb> </span><span style=color:#666>5</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>matchLabels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app.kubernetes.io/name</span>:<span style=color:#bbb> </span>load-balancer-example<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>template</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app.kubernetes.io/name</span>:<span style=color:#bbb> </span>load-balancer-example<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>gcr.io/google-samples/hello-app:2.0<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>hello-world<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>containerPort</span>:<span style=color:#bbb> </span><span style=color:#666>8080</span><span style=color:#bbb> </span></span></span></code></pre></div></div></div><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/service/load-balancer-example.yaml </span></span></code></pre></div><p>前面的命令创建一个 <a class=glossary-tooltip title=管理集群上的多副本应用。 data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/workloads/controllers/deployment/ target=_blank aria-label=Deployment>Deployment</a> 对象和一个关联的 <a class=glossary-tooltip title='ReplicaSet 是下一代副本控制器。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/workloads/controllers/replicaset/ target=_blank aria-label=ReplicaSet>ReplicaSet</a> 对象。 ReplicaSet 有五个 <a class=glossary-tooltip title='Pod 表示你的集群上一组正在运行的容器。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/workloads/pods/ target=_blank aria-label=Pod>Pod</a>, 每个都运行 Hello World 应用。</p></li></ol><ol start=2><li><p>显示有关 Deployment 的信息:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get deployments hello-world </span></span><span style=display:flex><span>kubectl describe deployments hello-world </span></span></code></pre></div></li></ol><ol start=3><li><p>显示有关 ReplicaSet 对象的信息:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get replicasets </span></span><span style=display:flex><span>kubectl describe replicasets </span></span></code></pre></div></li></ol><ol start=4><li><p>创建公开 Deployment 的 Service 对象:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl expose deployment hello-world --type<span style=color:#666>=</span>LoadBalancer --name<span style=color:#666>=</span>my-service </span></span></code></pre></div></li></ol><ol start=5><li><p>显示有关 Service 的信息:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get services my-service </span></span></code></pre></div><p>输出类似于:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-console data-lang=console><span style=display:flex><span><span style=color:#888>NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE </span></span></span><span style=display:flex><span><span style=color:#888>my-service LoadBalancer 10.3.245.137 104.198.205.71 8080/TCP 54s </span></span></span></code></pre></div><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p><code>type=LoadBalancer</code> 服务由外部云服务提供商提供支持,本例中不包含此部分, 详细信息请参考<a href=/zh-cn/docs/concepts/services-networking/service/#loadbalancer>此页</a></p></div><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>如果外部 IP 地址显示为 <pending>,请等待一分钟再次输入相同的命令。</p></div></li></ol><ol start=6><li><p>显示有关 Service 的详细信息:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl describe services my-service </span></span></code></pre></div><p>输出类似于:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-console data-lang=console><span style=display:flex><span><span style=color:#888>Name: my-service </span></span></span><span style=display:flex><span><span style=color:#888>Namespace: default </span></span></span><span style=display:flex><span><span style=color:#888>Labels: app.kubernetes.io/name=load-balancer-example </span></span></span><span style=display:flex><span><span style=color:#888>Annotations: <none> </span></span></span><span style=display:flex><span><span style=color:#888>Selector: app.kubernetes.io/name=load-balancer-example </span></span></span><span style=display:flex><span><span style=color:#888>Type: LoadBalancer </span></span></span><span style=display:flex><span><span style=color:#888>IP: 10.3.245.137 </span></span></span><span style=display:flex><span><span style=color:#888>LoadBalancer Ingress: 104.198.205.71 </span></span></span><span style=display:flex><span><span style=color:#888>Port: <unset> 8080/TCP </span></span></span><span style=display:flex><span><span style=color:#888>NodePort: <unset> 32377/TCP </span></span></span><span style=display:flex><span><span style=color:#888>Endpoints: 10.0.0.6:8080,10.0.1.6:8080,10.0.1.7:8080 + 2 more... </span></span></span><span style=display:flex><span><span style=color:#888>Session Affinity: None </span></span></span><span style=display:flex><span><span style=color:#888>Events: <none> </span></span></span></code></pre></div><p>记下服务公开的外部 IP 地址(<code>LoadBalancer Ingress</code>)。 在本例中,外部 IP 地址是 104.198.205.71。还要注意 <code>Port</code> 和 <code>NodePort</code> 的值。 在本例中,<code>Port</code> 是 8080,<code>NodePort</code> 是 32377。</p></li></ol><ol start=7><li><p>在前面的输出中,你可以看到服务有几个端点: 10.0.0.6:8080、10.0.1.6:8080、10.0.1.7:8080 和另外两个, 这些都是正在运行 Hello World 应用的 Pod 的内部地址。 要验证这些是 Pod 地址,请输入以下命令:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods --output<span style=color:#666>=</span>wide </span></span></code></pre></div><p>输出类似于:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-console data-lang=console><span style=display:flex><span><span style=color:#888>NAME ... IP NODE </span></span></span><span style=display:flex><span><span style=color:#888>hello-world-2895499144-1jaz9 ... 10.0.1.6 gke-cluster-1-default-pool-e0b8d269-1afc </span></span></span><span style=display:flex><span><span style=color:#888>hello-world-2895499144-2e5uh ... 10.0.1.8 gke-cluster-1-default-pool-e0b8d269-1afc </span></span></span><span style=display:flex><span><span style=color:#888>hello-world-2895499144-9m4h1 ... 10.0.0.6 gke-cluster-1-default-pool-e0b8d269-5v7a </span></span></span><span style=display:flex><span><span style=color:#888>hello-world-2895499144-o4z13 ... 10.0.1.7 gke-cluster-1-default-pool-e0b8d269-1afc </span></span></span><span style=display:flex><span><span style=color:#888>hello-world-2895499144-segjf ... 10.0.2.5 gke-cluster-1-default-pool-e0b8d269-cpuc </span></span></span></code></pre></div></li></ol><ol start=8><li><p>使用外部 IP 地址(<code>LoadBalancer Ingress</code>)访问 Hello World 应用:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>curl http://<external-ip>:<port> </span></span></code></pre></div><p>其中 <code><external-ip></code> 是你的服务的外部 IP 地址(<code>LoadBalancer Ingress</code>), <code><port></code> 是你的服务描述中的 <code>port</code> 的值。 如果你正在使用 minikube,输入 <code>minikube service my-service</code> 将在浏览器中自动打开 Hello World 应用。</p><p>成功请求的响应是一条问候消息:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>Hello, world! </span></span><span style=display:flex><span>Version: 2.0.0 </span></span><span style=display:flex><span>Hostname: 0bd46b45f32f </span></span></code></pre></div></li></ol><h2 id=清理现场>清理现场</h2><p>要删除 Service,请输入以下命令:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete services my-service </span></span></code></pre></div><p>要删除正在运行 Hello World 应用的 Deployment、ReplicaSet 和 Pod,请输入以下命令:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete deployment hello-world </span></span></code></pre></div><h2 id=接下来>接下来</h2><p>进一步了解<a href=/zh-cn/docs/tutorials/services/connect-applications-service/>使用 Service 连接到应用</a>。</p></div><div class=td-content style=page-break-before:always><h1 id=pg-8c56795c6614cc5f52434ecc756448ac>5.2 - 示例:使用 Redis 部署 PHP 留言板应用</h1><p>本教程向你展示如何使用 Kubernetes 和 <a href=https://www.docker.com/>Docker</a> 构建和部署一个简单的 <strong>(非面向生产的)</strong> 多层 Web 应用。本例由以下组件组成:</p><ul><li>单实例 <a href=https://www.redis.io/>Redis</a> 以保存留言板条目</li><li>多个 Web 前端实例</li></ul><h2 id=教程目标>教程目标</h2><ul><li>启动 Redis 领导者(Leader)</li><li>启动两个 Redis 跟随者(Follower)</li><li>公开并查看前端服务</li><li>清理</li></ul><h2 id=准备开始>准备开始</h2><p>你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 <a href=https://minikube.sigs.k8s.io/docs/tutorials/multi_node/>Minikube</a> 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:</p><ul><li><a href=https://killercoda.com/playgrounds/scenario/kubernetes>Killercoda</a></li><li><a href=https://labs.play-with-k8s.com/>玩转 Kubernetes</a></li></ul>你的 Kubernetes 服务器版本必须不低于版本 v1.14. 要获知版本信息,请输入 <code>kubectl version</code>.<h2 id=start-up-the-redis-database>启动 Redis 数据库</h2><p>留言板应用使用 Redis 存储数据。</p><h3 id=creating-the-redis-deployment>创建 Redis Deployment</h3><p>下面包含的清单文件指定了一个 Deployment 控制器,该控制器运行一个 Redis Pod 副本。</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/application/guestbook/redis-leader-deployment.yaml download=application/guestbook/redis-leader-deployment.yaml><code>application/guestbook/redis-leader-deployment.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("application-guestbook-redis-leader-deployment-yaml")' title="复制 application/guestbook/redis-leader-deployment.yaml 到剪贴板"></img></div><div class=includecode id=application-guestbook-redis-leader-deployment-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:#080;font-style:italic># 来源:https://cloud.google.com/kubernetes-engine/docs/tutorials/guestbook</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>apps/v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Deployment<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>redis-leader<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>redis<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>role</span>:<span style=color:#bbb> </span>leader<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>tier</span>:<span style=color:#bbb> </span>backend<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>replicas</span>:<span style=color:#bbb> </span><span style=color:#666>1</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>matchLabels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>redis<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>template</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>redis<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>role</span>:<span style=color:#bbb> </span>leader<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>tier</span>:<span style=color:#bbb> </span>backend<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>leader<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span><span style=color:#b44>"docker.io/redis:6.0.5"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>resources</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>requests</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>cpu</span>:<span style=color:#bbb> </span>100m<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>memory</span>:<span style=color:#bbb> </span>100Mi<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>containerPort</span>:<span style=color:#bbb> </span><span style=color:#666>6379</span></span></span></code></pre></div></div></div><ol><li><p>在下载清单文件的目录中启动终端窗口。</p></li><li><p>从 <code>redis-leader-deployment.yaml</code> 文件中应用 Redis Deployment:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/application/guestbook/redis-leader-deployment.yaml </span></span></code></pre></div></li></ol><ol start=3><li><p>查询 Pod 列表以验证 Redis Pod 是否正在运行:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods </span></span></code></pre></div><p>响应应该与此类似:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>NAME READY STATUS RESTARTS AGE </span></span><span style=display:flex><span>redis-leader-fb76b4755-xjr2n 1/1 Running <span style=color:#666>0</span> 13s </span></span></code></pre></div></li></ol><ol start=4><li><p>运行以下命令查看 Redis Deployment 中的日志:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl logs -f deployment/redis-leader </span></span></code></pre></div></li></ol><h3 id=creating-the-redis-leader-service>创建 Redis 领导者服务</h3><p>留言板应用需要往 Redis 中写数据。因此,需要创建 <a href=/zh-cn/docs/concepts/services-networking/service/>Service</a> 来转发 Redis Pod 的流量。Service 定义了访问 Pod 的策略。</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/application/guestbook/redis-leader-service.yaml download=application/guestbook/redis-leader-service.yaml><code>application/guestbook/redis-leader-service.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("application-guestbook-redis-leader-service-yaml")' title="复制 application/guestbook/redis-leader-service.yaml 到剪贴板"></img></div><div class=includecode id=application-guestbook-redis-leader-service-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:#080;font-style:italic># 来源:https://cloud.google.com/kubernetes-engine/docs/tutorials/guestbook</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Service<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>redis-leader<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>redis<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>role</span>:<span style=color:#bbb> </span>leader<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>tier</span>:<span style=color:#bbb> </span>backend<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>port</span>:<span style=color:#bbb> </span><span style=color:#666>6379</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>targetPort</span>:<span style=color:#bbb> </span><span style=color:#666>6379</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>redis<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>role</span>:<span style=color:#bbb> </span>leader<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>tier</span>:<span style=color:#bbb> </span>backend</span></span></code></pre></div></div></div><ol><li><p>使用下面的 <code>redis-leader-service.yaml</code> 文件创建 Redis 的服务:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/application/guestbook/redis-leader-service.yaml </span></span></code></pre></div></li></ol><ol start=2><li><p>查询服务列表验证 Redis 服务是否正在运行:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get service </span></span></code></pre></div><p>响应应该与此类似:</p><pre tabindex=0><code>NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 1m redis-leader ClusterIP 10.103.78.24 <none> 6379/TCP 16s </code></pre></li></ol><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>这个清单文件创建了一个名为 <code>redis-leader</code> 的 Service, 其中包含一组与前面定义的标签匹配的标签,因此服务将网络流量路由到 Redis Pod 上。</p></div><h3 id=set-up-redis-followers>设置 Redis 跟随者</h3><p>尽管 Redis 领导者只有一个 Pod,你可以通过添加若干 Redis 跟随者来将其配置为高可用状态, 以满足流量需求。</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/application/guestbook/redis-follower-deployment.yaml download=application/guestbook/redis-follower-deployment.yaml><code>application/guestbook/redis-follower-deployment.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("application-guestbook-redis-follower-deployment-yaml")' title="复制 application/guestbook/redis-follower-deployment.yaml 到剪贴板"></img></div><div class=includecode id=application-guestbook-redis-follower-deployment-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:#080;font-style:italic># 来源:https://cloud.google.com/kubernetes-engine/docs/tutorials/guestbook</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>apps/v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Deployment<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>redis-follower<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>redis<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>role</span>:<span style=color:#bbb> </span>follower<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>tier</span>:<span style=color:#bbb> </span>backend<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>replicas</span>:<span style=color:#bbb> </span><span style=color:#666>2</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>matchLabels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>redis<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>template</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>redis<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>role</span>:<span style=color:#bbb> </span>follower<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>tier</span>:<span style=color:#bbb> </span>backend<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>follower<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>us-docker.pkg.dev/google-samples/containers/gke/gb-redis-follower:v2<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>resources</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>requests</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>cpu</span>:<span style=color:#bbb> </span>100m<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>memory</span>:<span style=color:#bbb> </span>100Mi<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>containerPort</span>:<span style=color:#bbb> </span><span style=color:#666>6379</span></span></span></code></pre></div></div></div><ol><li><p>应用下面的 <code>redis-follower-deployment.yaml</code> 文件创建 Redis Deployment:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/application/guestbook/redis-follower-deployment.yaml </span></span></code></pre></div></li></ol><ol start=2><li><p>通过查询 Pods 列表,验证两个 Redis 跟随者副本在运行:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods </span></span></code></pre></div><p>响应应该类似于这样:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE redis-follower-dddfbdcc9-82sfr 1/1 Running 0 37s redis-follower-dddfbdcc9-qrt5k 1/1 Running 0 38s redis-leader-fb76b4755-xjr2n 1/1 Running 0 11m </code></pre></li></ol><h3 id=creating-the-redis-follower-service>创建 Redis 跟随者服务</h3><p>Guestbook 应用需要与 Redis 跟随者通信以读取数据。 为了让 Redis 跟随者可被发现,你必须创建另一个 <a href=/zh-cn/docs/concepts/services-networking/service/>Service</a>。</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/application/guestbook/redis-follower-service.yaml download=application/guestbook/redis-follower-service.yaml><code>application/guestbook/redis-follower-service.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("application-guestbook-redis-follower-service-yaml")' title="复制 application/guestbook/redis-follower-service.yaml 到剪贴板"></img></div><div class=includecode id=application-guestbook-redis-follower-service-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:#080;font-style:italic># 来源:https://cloud.google.com/kubernetes-engine/docs/tutorials/guestbook</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Service<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>redis-follower<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>redis<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>role</span>:<span style=color:#bbb> </span>follower<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>tier</span>:<span style=color:#bbb> </span>backend<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:#080;font-style:italic># 此服务应使用的端口</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>port</span>:<span style=color:#bbb> </span><span style=color:#666>6379</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>redis<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>role</span>:<span style=color:#bbb> </span>follower<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>tier</span>:<span style=color:#bbb> </span>backend</span></span></code></pre></div></div></div><ol><li><p>应用如下所示 <code>redis-follower-service.yaml</code> 文件中的 Redis Service:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/application/guestbook/redis-follower-service.yaml </span></span></code></pre></div></li></ol><ol start=2><li><p>查询 Service 列表,验证 Redis 服务在运行:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get service </span></span></code></pre></div><p>响应应该类似于这样:</p><pre tabindex=0><code>NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d19h redis-follower ClusterIP 10.110.162.42 <none> 6379/TCP 9s redis-leader ClusterIP 10.103.78.24 <none> 6379/TCP 6m10s </code></pre></li></ol><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>清单文件创建了一个名为 <code>redis-follower</code> 的 Service,该 Service 具有一些与之前所定义的标签相匹配的标签,因此该 Service 能够将网络流量路由到 Redis Pod 之上。</p></div><h2 id=set-up-and-expose-the-guestbook-frontend>设置并公开留言板前端</h2><p>现在你有了一个为 Guestbook 应用配置的 Redis 存储处于运行状态, 接下来可以启动 Guestbook 的 Web 服务器了。 与 Redis 跟随者类似,前端也是使用 Kubernetes Deployment 来部署的。</p><p>Guestbook 应用使用 PHP 前端。该前端被配置成与后端的 Redis 跟随者或者领导者服务通信,具体选择哪个服务取决于请求是读操作还是写操作。 前端对外暴露一个 JSON 接口,并提供基于 jQuery-Ajax 的用户体验。</p><h3 id=creating-the-guestbook-frontend-deployment>创建 Guestbook 前端 Deployment</h3><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/application/guestbook/frontend-deployment.yaml download=application/guestbook/frontend-deployment.yaml><code>application/guestbook/frontend-deployment.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("application-guestbook-frontend-deployment-yaml")' title="复制 application/guestbook/frontend-deployment.yaml 到剪贴板"></img></div><div class=includecode id=application-guestbook-frontend-deployment-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:#080;font-style:italic># 来源:https://cloud.google.com/kubernetes-engine/docs/tutorials/guestbook</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>apps/v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Deployment<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>frontend<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>replicas</span>:<span style=color:#bbb> </span><span style=color:#666>3</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>matchLabels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>guestbook<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>tier</span>:<span style=color:#bbb> </span>frontend<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>template</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>guestbook<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>tier</span>:<span style=color:#bbb> </span>frontend<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>php-redis<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>us-docker.pkg.dev/google-samples/containers/gke/gb-frontend:v5<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>env</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>GET_HOSTS_FROM<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>value</span>:<span style=color:#bbb> </span><span style=color:#b44>"dns"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>resources</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>requests</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>cpu</span>:<span style=color:#bbb> </span>100m<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>memory</span>:<span style=color:#bbb> </span>100Mi<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>containerPort</span>:<span style=color:#bbb> </span><span style=color:#666>80</span></span></span></code></pre></div></div></div><ol><li><p>应用来自 <code>frontend-deployment.yaml</code> 文件的前端 Deployment:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/application/guestbook/frontend-deployment.yaml </span></span></code></pre></div></li></ol><ol start=2><li><p>查询 Pod 列表,验证三个前端副本正在运行:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>guestbook -l <span style=color:#b8860b>tier</span><span style=color:#666>=</span>frontend </span></span></code></pre></div><p>响应应该与此类似:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE frontend-85595f5bf9-5tqhb 1/1 Running 0 47s frontend-85595f5bf9-qbzwm 1/1 Running 0 47s frontend-85595f5bf9-zchwc 1/1 Running 0 47s </code></pre></li></ol><h3 id=creating-the-frontend-service>创建前端服务</h3><p>应用的 <code>Redis</code> 服务只能在 Kubernetes 集群中访问,因为服务的默认类型是 <a href=/zh-cn/docs/concepts/services-networking/service/#publishing-services-service-types>ClusterIP</a>。 <code>ClusterIP</code> 为服务指向的 Pod 集提供一个 IP 地址。这个 IP 地址只能在集群中访问。</p><p>如果你希望访客能够访问你的 Guestbook,你必须将前端服务配置为外部可见的, 以便客户端可以从 Kubernetes 集群之外请求服务。 然而即便使用了 <code>ClusterIP</code>,Kubernetes 用户仍可以通过 <code>kubectl port-forward</code> 访问服务。</p><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>Google Compute Engine 或 Google Kubernetes Engine 这些云平台支持外部负载均衡器。如果你的云平台支持负载均衡器,并且你希望使用它, 只需取消注释 <code>type: LoadBalancer</code>。</p></div><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/application/guestbook/frontend-service.yaml download=application/guestbook/frontend-service.yaml><code>application/guestbook/frontend-service.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("application-guestbook-frontend-service-yaml")' title="复制 application/guestbook/frontend-service.yaml 到剪贴板"></img></div><div class=includecode id=application-guestbook-frontend-service-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:#080;font-style:italic># 来源:https://cloud.google.com/kubernetes-engine/docs/tutorials/guestbook</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Service<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>frontend<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>guestbook<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>tier</span>:<span style=color:#bbb> </span>frontend<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:#080;font-style:italic># 如果你的集群支持,请取消注释以下内容以自动为前端服务创建一个外部负载均衡 IP。</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:#080;font-style:italic># type: LoadBalancer</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:#080;font-style:italic>#type: LoadBalancer</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:#080;font-style:italic># 此服务应使用的端口</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>port</span>:<span style=color:#bbb> </span><span style=color:#666>80</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>guestbook<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>tier</span>:<span style=color:#bbb> </span>frontend</span></span></code></pre></div></div></div><ol><li><p>应用来自 <code>frontend-service.yaml</code> 文件中的前端服务:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/application/guestbook/frontend-service.yaml </span></span></code></pre></div></li></ol><ol start=2><li><p>查询 Service 列表以验证前端服务正在运行:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get services </span></span></code></pre></div><p>响应应该与此类似:</p><pre tabindex=0><code>NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE frontend ClusterIP 10.97.28.230 <none> 80/TCP 19s kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d19h redis-follower ClusterIP 10.110.162.42 <none> 6379/TCP 5m48s redis-leader ClusterIP 10.103.78.24 <none> 6379/TCP 11m </code></pre></li></ol><h3 id=viewing-the-frontend-service-via-kubectl-port-forward>通过 <code>kubectl port-forward</code> 查看前端服务</h3><ol><li><p>运行以下命令将本机的 <code>8080</code> 端口转发到服务的 <code>80</code> 端口。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl port-forward svc/frontend 8080:80 </span></span></code></pre></div><p>响应应该与此类似:</p><pre tabindex=0><code>Forwarding from 127.0.0.1:8080 -> 80 Forwarding from [::1]:8080 -> 80 </code></pre></li></ol><ol start=2><li>在浏览器中加载 <a href=http://localhost:8080>http://localhost:8080</a> 页面以查看 Guestbook。</li></ol><h3 id=viewing-the-frontend-service-via-loadbalancer>通过 <code>LoadBalancer</code> 查看前端服务</h3><p>如果你部署了 <code>frontend-service.yaml</code>,需要找到用来查看 Guestbook 的 IP 地址。</p><ol><li><p>运行以下命令以获取前端服务的 IP 地址。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get service frontend </span></span></code></pre></div><p>响应应该与此类似:</p><pre tabindex=0><code>NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE frontend LoadBalancer 10.51.242.136 109.197.92.229 80:32372/TCP 1m </code></pre></li></ol><ol start=2><li>复制这里的外部 IP 地址,然后在浏览器中加载页面以查看留言板。</li></ol><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>尝试通过输入消息并点击 Submit 来添加一些留言板条目。 你所输入的消息会在前端显示。这一消息表明数据被通过你之前所创建的 Service 添加到 Redis 存储中。</p></div><h2 id=scale-the-web-frontend>扩展 Web 前端</h2><p>你可以根据需要执行伸缩操作,这是因为服务器本身被定义为使用一个 Deployment 控制器的 Service。</p><ol><li><p>运行以下命令扩展前端 Pod 的数量:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl scale deployment frontend --replicas<span style=color:#666>=</span><span style=color:#666>5</span> </span></span></code></pre></div></li></ol><ol start=2><li><p>查询 Pod 列表验证正在运行的前端 Pod 的数量:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods </span></span></code></pre></div><p>响应应该类似于这样:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE frontend-85595f5bf9-5df5m 1/1 Running 0 83s frontend-85595f5bf9-7zmg5 1/1 Running 0 83s frontend-85595f5bf9-cpskg 1/1 Running 0 15m frontend-85595f5bf9-l2l54 1/1 Running 0 14m frontend-85595f5bf9-l9c8z 1/1 Running 0 14m redis-follower-dddfbdcc9-82sfr 1/1 Running 0 97m redis-follower-dddfbdcc9-qrt5k 1/1 Running 0 97m redis-leader-fb76b4755-xjr2n 1/1 Running 0 108m </code></pre></li></ol><ol start=3><li><p>运行以下命令缩小前端 Pod 的数量:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl scale deployment frontend --replicas<span style=color:#666>=</span><span style=color:#666>2</span> </span></span></code></pre></div></li></ol><ol start=4><li><p>查询 Pod 列表验证正在运行的前端 Pod 的数量:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods </span></span></code></pre></div><p>响应应该类似于这样:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE frontend-85595f5bf9-cpskg 1/1 Running 0 16m frontend-85595f5bf9-l9c8z 1/1 Running 0 15m redis-follower-dddfbdcc9-82sfr 1/1 Running 0 98m redis-follower-dddfbdcc9-qrt5k 1/1 Running 0 98m redis-leader-fb76b4755-xjr2n 1/1 Running 0 109m </code></pre></li></ol><h2 id=清理现场>清理现场</h2><p>删除 Deployments 和服务还会删除正在运行的 Pod。 使用标签用一个命令删除多个资源。</p><ol><li><p>运行以下命令以删除所有 Pod、Deployment 和 Service。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete deployment -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>redis </span></span><span style=display:flex><span>kubectl delete service -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>redis </span></span><span style=display:flex><span>kubectl delete deployment frontend </span></span><span style=display:flex><span>kubectl delete service frontend </span></span></code></pre></div><p>响应应该是:</p><pre tabindex=0><code>deployment.apps "redis-follower" deleted deployment.apps "redis-leader" deleted deployment.apps "frontend" deleted service "frontend" deleted </code></pre></li></ol><ol start=2><li><p>查询 Pod 列表,确认没有 Pod 在运行:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods </span></span></code></pre></div><p>响应应该是:</p><pre tabindex=0><code>No resources found in default namespace. </code></pre></li></ol><h2 id=接下来>接下来</h2><ul><li>完成 <a href=/zh-cn/docs/tutorials/kubernetes-basics/>Kubernetes 基础</a> 交互式教程</li><li>使用 Kubernetes 创建一个博客,使用 <a href=/zh-cn/docs/tutorials/stateful-application/mysql-wordpress-persistent-volume/#visit-your-new-wordpress-blog>MySQL 和 Wordpress 的持久卷</a></li><li>进一步阅读<a href=/zh-cn/docs/tutorials/services/connect-applications-service/>使用 Service 连接到应用</a></li><li>进一步阅读<a href=/zh-cn/docs/concepts/overview/working-with-objects/labels/#using-labels-effectively>有效使用标签</a></li></ul></div><div class=td-content style=page-break-before:always><h1 id=pg-d6336d9712aa433eb5f0fb8cbed6bef7>6 - 有状态的应用</h1></div><div class=td-content><h1 id=pg-42e39658021b706bcc9478c8cc73c4a3>6.1 - StatefulSet 基础</h1><p>本教程介绍了如何使用 <a class=glossary-tooltip title='StatefulSet 用来管理某 Pod 集合的部署和扩缩,并为这些 Pod 提供持久存储和持久标识符。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/workloads/controllers/statefulset/ target=_blank aria-label=StatefulSet>StatefulSet</a> 来管理应用。 演示了如何创建、删除、扩容/缩容和更新 StatefulSet 的 Pod。</p><h2 id=准备开始>准备开始</h2><p>在开始本教程之前,你应该熟悉以下 Kubernetes 的概念:</p><ul><li><a href=/zh-cn/docs/concepts/workloads/pods/>Pod</a></li><li><a href=/zh-cn/docs/concepts/services-networking/dns-pod-service/>Cluster DNS</a></li><li><a href=/zh-cn/docs/concepts/services-networking/service/#headless-services>Headless Service</a></li><li><a href=/zh-cn/docs/concepts/storage/persistent-volumes/>PersistentVolumes</a></li><li><a href=https://github.com/kubernetes/examples/tree/master/staging/persistent-volume-provisioning/>PersistentVolume Provisioning</a></li><li><a href=/zh-cn/docs/reference/kubectl/kubectl/>kubectl</a> 命令行工具</li></ul><p>你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 <a href=https://minikube.sigs.k8s.io/docs/tutorials/multi_node/>Minikube</a> 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:</p><ul><li><a href=https://killercoda.com/playgrounds/scenario/kubernetes>Killercoda</a></li><li><a href=https://labs.play-with-k8s.com/>玩转 Kubernetes</a></li></ul><p>你应该配置 <code>kubectl</code> 的上下文使用 <code>default</code> 命名空间。 如果你使用的是现有集群,请确保可以使用该集群的 <code>default</code> 命名空间进行练习。 理想情况下,在没有运行任何实际工作负载的集群中进行练习。</p><p>阅读有关 <a href=/zh-cn/docs/concepts/workloads/controllers/statefulset/>StatefulSet</a> 的概念页面也很有用。</p><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>本教程假设你的集群被配置为动态制备 PersistentVolume 卷, 且有一个<a href=/zh-cn/docs/concepts/storage/storage-classes/#default-storageclass>默认 StorageClass</a>。 如果没有这样配置,在开始本教程之前,你需要手动准备 2 个 1 GiB 的存储卷, 以便这些 PersistentVolume 可以映射到 StatefulSet 定义的 PersistentVolumeClaim 模板。</p></div><h2 id=教程目标>教程目标</h2><p>StatefulSet 旨在与有状态的应用及分布式系统一起使用。然而在 Kubernetes 上管理有状态应用和分布式系统是一个宽泛而复杂的话题。 为了演示 StatefulSet 的基本特性,并且不使前后的主题混淆,你将会使用 StatefulSet 部署一个简单的 Web 应用。</p><p>在阅读本教程后,你将熟悉以下内容:</p><ul><li>如何创建 StatefulSet</li><li>StatefulSet 怎样管理它的 Pod</li><li>如何删除 StatefulSet</li><li>如何对 StatefulSet 进行扩容/缩容</li><li>如何更新一个 StatefulSet 的 Pod</li></ul><h2 id=creating-a-statefulset>创建 StatefulSet</h2><p>作为开始,使用如下示例创建一个 StatefulSet(以及它所依赖的 Service)。它和 <a href=/zh-cn/docs/concepts/workloads/controllers/statefulset/>StatefulSet</a> 概念中的示例相似。 它创建了一个 <a href=/zh-cn/docs/concepts/services-networking/service/#headless-services>Headless Service</a> <code>nginx</code> 用来发布 StatefulSet <code>web</code> 中的 Pod 的 IP 地址。</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/application/web/web.yaml download=application/web/web.yaml><code>application/web/web.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("application-web-web-yaml")' title="复制 application/web/web.yaml 到剪贴板"></img></div><div class=includecode id=application-web-web-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Service<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>port</span>:<span style=color:#bbb> </span><span style=color:#666>80</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>web<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>clusterIP</span>:<span style=color:#bbb> </span>None<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:#00f;font-weight:700>---</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>apps/v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>StatefulSet<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>web<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>serviceName</span>:<span style=color:#bbb> </span><span style=color:#b44>"nginx"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>replicas</span>:<span style=color:#bbb> </span><span style=color:#666>2</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>matchLabels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>template</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>registry.k8s.io/nginx-slim:0.21<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>containerPort</span>:<span style=color:#bbb> </span><span style=color:#666>80</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>web<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumeMounts</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>www<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>mountPath</span>:<span style=color:#bbb> </span>/usr/share/nginx/html<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumeClaimTemplates</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>www<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>accessModes</span>:<span style=color:#bbb> </span>[<span style=color:#bbb> </span><span style=color:#b44>"ReadWriteOnce"</span><span style=color:#bbb> </span>]<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>resources</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>requests</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>storage</span>:<span style=color:#bbb> </span>1Gi<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span></span></span></code></pre></div></div></div><p>你需要使用至少两个终端窗口。在第一个终端中,使用 <a href=/docs/reference/generated/kubectl/kubectl-commands/#get><code>kubectl get</code></a> 来<a class=glossary-tooltip title='用于以流的形式跟踪 Kubernetes 中对象变化的动词。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/reference/using-api/api-concepts/#api-verbs target=_blank aria-label=监视>监视</a> StatefulSet 的 Pod 的创建情况。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 使用此终端运行指定 --watch 的命令</span> </span></span><span style=display:flex><span><span style=color:#080;font-style:italic># 当你被要求开始一个新的 watch 时结束这个 watch</span> </span></span><span style=display:flex><span>kubectl get pods --watch -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx </span></span></code></pre></div><p>在另一个终端中,使用 <a href=/docs/reference/generated/kubectl/kubectl-commands/#apply><code>kubectl apply</code></a> 来创建 Headless Service 和 StatefulSet。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/application/web/web.yaml </span></span></code></pre></div><pre tabindex=0><code>service/nginx created statefulset.apps/web created </code></pre><p>上面的命令创建了两个 Pod,每个都运行了一个 <a href=https://www.nginx.com>NginX</a> Web 服务器。 获取 <code>nginx</code> Service:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get service nginx </span></span></code></pre></div><pre tabindex=0><code>NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx ClusterIP None <none> 80/TCP 12s </code></pre><p>然后获取 <code>web</code> StatefulSet,以验证两者均已成功创建:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get statefulset web </span></span></code></pre></div><pre tabindex=0><code>NAME READY AGE web 2/2 37s </code></pre><h3 id=ordered-pod-creation>顺序创建 Pod</h3><p>StatefulSet 默认以严格的顺序创建其 Pod。</p><p>对于一个拥有 <strong>n</strong> 个副本的 StatefulSet,Pod 被部署时是按照 <strong>{0..n-1}</strong> 的序号顺序创建的。 在第一个终端中使用 <code>kubectl get</code> 检查输出。这个输出最终将看起来像下面的样子。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 不要开始一个新的 watch</span> </span></span><span style=display:flex><span><span style=color:#080;font-style:italic># 这应该已经处于 Running 状态</span> </span></span><span style=display:flex><span>kubectl get pods --watch -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx </span></span></code></pre></div><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE web-0 0/1 Pending 0 0s web-0 0/1 Pending 0 0s web-0 0/1 ContainerCreating 0 0s web-0 1/1 Running 0 19s web-1 0/1 Pending 0 0s web-1 0/1 Pending 0 0s web-1 0/1 ContainerCreating 0 0s web-1 1/1 Running 0 18s </code></pre><p>请注意,直到 <code>web-0</code> Pod 处于 <strong>Running</strong>(请参阅 <a href=/zh-cn/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase>Pod 阶段</a>) 并 <strong>Ready</strong>(请参阅 <a href=/zh-cn/docs/concepts/workloads/pods/pod-lifecycle/#pod-conditions>Pod 状况</a>中的 <code>type</code>)状态后,<code>web-1</code> Pod 才会被启动。</p><p>在本教程的后面部分,你将练习<a href=#parallel-pod-management>并行启动</a>。</p><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>要配置分配给 StatefulSet 中每个 Pod 的整数序号, 请参阅<a href=/zh-cn/docs/concepts/workloads/controllers/statefulset/#start-ordinal>起始序号</a>。</p></div><h2 id=pods-in-a-statefulset>StatefulSet 中的 Pod</h2><p>StatefulSet 中的每个 Pod 拥有一个唯一的顺序索引和稳定的网络身份标识。</p><h3 id=examining-the-pod-s-ordinal-index>检查 Pod 的顺序索引</h3><p>获取 StatefulSet 的 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx </span></span></code></pre></div><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 1m web-1 1/1 Running 0 1m </code></pre><p>如同 <a href=/zh-cn/docs/concepts/workloads/controllers/statefulset/>StatefulSet</a> 概念中所提到的, StatefulSet 中的每个 Pod 拥有一个具有黏性的、独一无二的身份标志。 这个标志基于 StatefulSet <a class=glossary-tooltip title='控制器通过 API 服务器监控集群的公共状态,并致力于将当前状态转变为期望的状态。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/architecture/controller/ target=_blank aria-label=控制器>控制器</a>分配给每个 Pod 的唯一顺序索引。 Pod 名称的格式为 <code><statefulset 名称>-<序号索引></code>。 <code>web</code> StatefulSet 拥有两个副本,所以它创建了两个 Pod:<code>web-0</code> 和 <code>web-1</code>。</p><h3 id=using-stable-network-identities>使用稳定的网络身份标识</h3><p>每个 Pod 都拥有一个基于其顺序索引的稳定的主机名。使用 <a href=/docs/reference/generated/kubectl/kubectl-commands/#exec><code>kubectl exec</code></a> 在每个 Pod 中执行 <code>hostname</code>:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#a2f;font-weight:700>for</span> i in <span style=color:#666>0</span> 1; <span style=color:#a2f;font-weight:700>do</span> kubectl <span style=color:#a2f>exec</span> <span style=color:#b44>"web-</span><span style=color:#b8860b>$i</span><span style=color:#b44>"</span> -- sh -c <span style=color:#b44>'hostname'</span>; <span style=color:#a2f;font-weight:700>done</span> </span></span></code></pre></div><pre tabindex=0><code>web-0 web-1 </code></pre><p>使用 <a href=/docs/reference/generated/kubectl/kubectl-commands/#run><code>kubectl run</code></a> 运行一个提供 <code>nslookup</code> 命令的容器,该命令来自于 <code>dnsutils</code> 包。 通过对 Pod 的主机名执行 <code>nslookup</code>,你可以检查这些主机名在集群内部的 DNS 地址:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl run -i --tty --image busybox:1.28 dns-test --restart<span style=color:#666>=</span>Never --rm </span></span></code></pre></div><p>这将启动一个新的 Shell。在新 Shell 中运行:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 在 dns-test 容器 Shell 中运行以下命令</span> </span></span><span style=display:flex><span>nslookup web-0.nginx </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>Server: 10.0.0.10 Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local Name: web-0.nginx Address 1: 10.244.1.6 nslookup web-1.nginx Server: 10.0.0.10 Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local Name: web-1.nginx Address 1: 10.244.2.6 </code></pre><p>(现在可以退出容器 Shell:<code>exit</code>)</p><p>Headless service 的 CNAME 指向 SRV 记录(记录每个 Running 和 Ready 状态的 Pod)。 SRV 记录指向一个包含 Pod IP 地址的记录表项。</p><p>在一个终端中监视 StatefulSet 的 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 启动一个新的 watch</span> </span></span><span style=display:flex><span><span style=color:#080;font-style:italic># 当你看到删除完成后结束这个 watch</span> </span></span><span style=display:flex><span>kubectl get pod --watch -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx </span></span></code></pre></div><p>在另一个终端中使用 <a href=/docs/reference/generated/kubectl/kubectl-commands/#delete><code>kubectl delete</code></a> 删除 StatefulSet 中所有的 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete pod -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx </span></span></code></pre></div><pre tabindex=0><code>pod "web-0" deleted pod "web-1" deleted </code></pre><p>等待 StatefulSet 重启它们,并且两个 Pod 都变成 Running 和 Ready 状态:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 这应该已经处于 Running 状态</span> </span></span><span style=display:flex><span>kubectl get pod --watch -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx </span></span></code></pre></div><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE web-0 0/1 ContainerCreating 0 0s NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 2s web-1 0/1 Pending 0 0s web-1 0/1 Pending 0 0s web-1 0/1 ContainerCreating 0 0s web-1 1/1 Running 0 34s </code></pre><p>使用 <code>kubectl exec</code> 和 <code>kubectl run</code> 查看 Pod 的主机名和集群内部的 DNS 表项。 首先,查看 Pod 的主机名:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#a2f;font-weight:700>for</span> i in <span style=color:#666>0</span> 1; <span style=color:#a2f;font-weight:700>do</span> kubectl <span style=color:#a2f>exec</span> web-<span style=color:#b8860b>$i</span> -- sh -c <span style=color:#b44>'hostname'</span>; <span style=color:#a2f;font-weight:700>done</span> </span></span></code></pre></div><pre tabindex=0><code>web-0 web-1 </code></pre><p>然后,运行:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl run -i --tty --image busybox:1.28 dns-test --restart<span style=color:#666>=</span>Never --rm </span></span></code></pre></div><p>这将启动一个新的 Shell。在新 Shell 中,运行:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 在 dns-test 容器 Shell 中运行以下命令</span> </span></span><span style=display:flex><span>nslookup web-0.nginx </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>Server: 10.0.0.10 Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local Name: web-0.nginx Address 1: 10.244.1.7 nslookup web-1.nginx Server: 10.0.0.10 Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local Name: web-1.nginx Address 1: 10.244.2.8 </code></pre><p>(现在可以退出容器 Shell:<code>exit</code>)</p><p>Pod 的序号、主机名、SRV 条目和记录名称没有改变,但和 Pod 相关联的 IP 地址可能发生了改变。 在本教程中使用的集群中它们就改变了。这就是为什么不要在其他应用中使用 StatefulSet 中特定 Pod 的 IP 地址进行连接,这点很重要 (可以通过解析 Pod 的主机名来连接到 Pod)。</p><h4 id=discovery-for-specific-pods-in-a-statefulset>发现 StatefulSet 中特定的 Pod</h4><p>如果你需要查找并连接一个 StatefulSet 的活动成员,你应该查询 Headless Service 的 CNAME。 和 CNAME 相关联的 SRV 记录只会包含 StatefulSet 中处于 Running 和 Ready 状态的 Pod。</p><p>如果你的应用已经实现了用于测试是否已存活(liveness)并就绪(readiness)的连接逻辑, 你可以使用 Pod 的 SRV 记录(<code>web-0.nginx.default.svc.cluster.local</code>、 <code>web-1.nginx.default.svc.cluster.local</code>)。因为它们是稳定的,并且当你的 Pod 的状态变为 Running 和 Ready 时,你的应用就能够发现它们的地址。</p><p>如果你的应用程序想要在 StatefulSet 中找到任一健康的 Pod, 且不需要跟踪每个特定的 Pod,你还可以连接到由该 StatefulSet 中的 Pod 关联的 <code>type: ClusterIP</code> Service 的 IP 地址。 你可以使用跟踪 StatefulSet 的同一 Service (StatefulSet 中 <code>serviceName</code> 所指定的)或选择正确的 Pod 集的单独 Service。</p><h3 id=writing-to-stable-storage>写入稳定的存储</h3><p>获取 <code>web-0</code> 和 <code>web-1</code> 的 PersistentVolumeClaims:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pvc -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>NAME STATUS VOLUME CAPACITY ACCESSMODES AGE www-web-0 Bound pvc-15c268c7-b507-11e6-932f-42010a800002 1Gi RWO 48s www-web-1 Bound pvc-15c79307-b507-11e6-932f-42010a800002 1Gi RWO 48s </code></pre><p>StatefulSet 控制器创建了两个 <a class=glossary-tooltip title=声明在持久卷中定义的存储资源,以便可以将其挂载为容器中的卷。 data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims target=_blank aria-label=PersistentVolumeClaims>PersistentVolumeClaims</a>, 绑定到两个 <a class=glossary-tooltip title='持久卷是代表集群中一块存储空间的 API 对象。 它是通用的、可插拔的、并且不受单个 Pod 生命周期约束的持久化资源。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/storage/persistent-volumes/ target=_blank aria-label=PersistentVolumes>PersistentVolumes</a>。</p><p>由于本教程使用的集群配置为动态制备 PersistentVolume 卷,所有的 PersistentVolume 卷都是自动创建和绑定的。</p><p>NginX Web 服务器默认会加载位于 <code>/usr/share/nginx/html/index.html</code> 的 index 文件。 StatefulSet <code>spec</code> 中的 <code>volumeMounts</code> 字段保证了 <code>/usr/share/nginx/html</code> 文件夹由一个 PersistentVolume 卷支持。</p><p>将 Pod 的主机名写入它们的 <code>index.html</code> 文件并验证 NginX Web 服务器使用该主机名提供服务:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#a2f;font-weight:700>for</span> i in <span style=color:#666>0</span> 1; <span style=color:#a2f;font-weight:700>do</span> kubectl <span style=color:#a2f>exec</span> <span style=color:#b44>"web-</span><span style=color:#b8860b>$i</span><span style=color:#b44>"</span> -- sh -c <span style=color:#b44>'echo "$(hostname)" > /usr/share/nginx/html/index.html'</span>; <span style=color:#a2f;font-weight:700>done</span> </span></span><span style=display:flex><span> </span></span><span style=display:flex><span><span style=color:#a2f;font-weight:700>for</span> i in <span style=color:#666>0</span> 1; <span style=color:#a2f;font-weight:700>do</span> kubectl <span style=color:#a2f>exec</span> -i -t <span style=color:#b44>"web-</span><span style=color:#b8860b>$i</span><span style=color:#b44>"</span> -- curl http://localhost/; <span style=color:#a2f;font-weight:700>done</span> </span></span></code></pre></div><pre tabindex=0><code>web-0 web-1 </code></pre><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>请注意,如果你看见上面的 curl 命令返回了 <strong>403 Forbidden</strong> 的响应,你需要像这样修复使用 <code>volumeMounts</code> (原因归咎于<a href=https://github.com/kubernetes/kubernetes/issues/2630>使用 hostPath 卷时存在的缺陷</a>) 挂载的目录的权限,先运行:</p><p><code>for i in 0 1; do kubectl exec web-$i -- chmod 755 /usr/share/nginx/html; done</code></p><p>再重新尝试上面的 <code>curl</code> 命令。</p></div><p>在一个终端监视 StatefulSet 的 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pod -w -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx </span></span></code></pre></div><p>在另一个终端删除 StatefulSet 所有的 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 当你到达该部分的末尾时结束此 watch</span> </span></span><span style=display:flex><span><span style=color:#080;font-style:italic># 在开始“扩展 StatefulSet” 时,你将启动一个新的 watch。</span> </span></span><span style=display:flex><span>kubectl get pod --watch -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx </span></span></code></pre></div><pre tabindex=0><code>pod "web-0" deleted pod "web-1" deleted </code></pre><p>在第一个终端里检查 <code>kubectl get</code> 命令的输出,等待所有 Pod 变成 Running 和 Ready 状态。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 这应该已经处于 Running 状态</span> </span></span><span style=display:flex><span>kubectl get pod --watch -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx </span></span></code></pre></div><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE web-0 0/1 ContainerCreating 0 0s NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 2s web-1 0/1 Pending 0 0s web-1 0/1 Pending 0 0s web-1 0/1 ContainerCreating 0 0s web-1 1/1 Running 0 34s </code></pre><p>验证所有 Web 服务器在继续使用它们的主机名提供服务:</p><pre tabindex=0><code>for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done </code></pre><pre tabindex=0><code>web-0 web-1 </code></pre><p>虽然 <code>web-0</code> 和 <code>web-1</code> 被重新调度了,但它们仍然继续监听各自的主机名,因为和它们的 PersistentVolumeClaim 相关联的 PersistentVolume 卷被重新挂载到了各自的 <code>volumeMount</code> 上。 不管 <code>web-0</code> 和 <code>web-1</code> 被调度到了哪个节点上,它们的 PersistentVolume 卷将会被挂载到合适的挂载点上。</p><h2 id=scaling-a-statefulset>扩容/缩容 StatefulSet</h2><p>扩容/缩容 StatefulSet 指增加或减少它的副本数。这通过更新 <code>replicas</code> 字段完成(水平缩放)。 你可以使用 <a href=/docs/reference/generated/kubectl/kubectl-commands/#scale><code>kubectl scale</code></a> 或者 <a href=/docs/reference/generated/kubectl/kubectl-commands/#patch><code>kubectl patch</code></a> 来扩容/缩容一个 StatefulSet。</p><h3 id=scaling-up>扩容</h3><p>扩容意味着添加更多副本。 如果你的应用程序能够在整个 StatefulSet 范围内分派工作,则新的更大的 Pod 集可以执行更多的工作。</p><p>在一个终端窗口监视 StatefulSet 的 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 如果你已经有一个正在运行的 wach,你可以继续使用它。</span> </span></span><span style=display:flex><span><span style=color:#080;font-style:italic># 否则,就启动一个。</span> </span></span><span style=display:flex><span><span style=color:#080;font-style:italic># 当 StatefulSet 有 5 个健康的 Pod 时结束此 watch</span> </span></span><span style=display:flex><span>kubectl get pods --watch -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx </span></span></code></pre></div><p>在另一个终端窗口使用 <code>kubectl scale</code> 扩展副本数为 5:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl scale sts web --replicas<span style=color:#666>=</span><span style=color:#666>5</span> </span></span></code></pre></div><pre tabindex=0><code>statefulset.apps/web scaled </code></pre><p>在第一个 终端中检查 <code>kubectl get</code> 命令的输出,等待增加的 3 个 Pod 的状态变为 Running 和 Ready。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 这应该已经处于 Running 状态</span> </span></span><span style=display:flex><span>kubectl get pod --watch -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx </span></span></code></pre></div><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 2h web-1 1/1 Running 0 2h NAME READY STATUS RESTARTS AGE web-2 0/1 Pending 0 0s web-2 0/1 Pending 0 0s web-2 0/1 ContainerCreating 0 0s web-2 1/1 Running 0 19s web-3 0/1 Pending 0 0s web-3 0/1 Pending 0 0s web-3 0/1 ContainerCreating 0 0s web-3 1/1 Running 0 18s web-4 0/1 Pending 0 0s web-4 0/1 Pending 0 0s web-4 0/1 ContainerCreating 0 0s web-4 1/1 Running 0 19s </code></pre><p>StatefulSet 控制器扩展了副本的数量。 如同<a href=#ordered-pod-creation>创建 StatefulSet</a> 所述,StatefulSet 按序号索引顺序创建各个 Pod,并且会等待前一个 Pod 变为 Running 和 Ready 才会启动下一个 Pod。</p><h3 id=scaling-down>缩容</h3><p>缩容意味着减少副本数量。 例如,你可能因为服务的流量水平已降低并且在当前规模下存在空闲资源的原因执行缩容操作。</p><p>在一个终端监视 StatefulSet 的 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods -w -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx </span></span><span style=display:flex><span><span style=color:#080;font-style:italic># 当 StatefulSet 只有 3 个 Pod 时结束此 watch</span> </span></span><span style=display:flex><span>kubectl get pod --watch -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx </span></span></code></pre></div><p>在另一个终端使用 <code>kubectl patch</code> 将 StatefulSet 缩容回三个副本:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl patch sts web -p <span style=color:#b44>'{"spec":{"replicas":3}}'</span> </span></span></code></pre></div><pre tabindex=0><code>statefulset.apps/web patched </code></pre><p>等待 <code>web-4</code> 和 <code>web-3</code> 状态变为 Terminating。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods -w -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx </span></span><span style=display:flex><span><span style=color:#080;font-style:italic># 这应该已经处于 Running 状态</span> </span></span><span style=display:flex><span>kubectl get pods --watch -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx </span></span></code></pre></div><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 3h web-1 1/1 Running 0 3h web-2 1/1 Running 0 55s web-3 1/1 Running 0 36s web-4 0/1 ContainerCreating 0 18s NAME READY STATUS RESTARTS AGE web-4 1/1 Running 0 19s web-4 1/1 Terminating 0 24s web-4 1/1 Terminating 0 24s web-3 1/1 Terminating 0 42s web-3 1/1 Terminating 0 42s </code></pre><h3 id=ordered-pod-termination>顺序终止 Pod</h3><p>控制器会按照与 Pod 序号索引相反的顺序每次删除一个 Pod。在删除下一个 Pod 前会等待上一个被完全关闭。</p><p>获取 StatefulSet 的 PersistentVolumeClaims:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pvc -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx </span></span></code></pre></div><pre tabindex=0><code>NAME STATUS VOLUME CAPACITY ACCESSMODES AGE www-web-0 Bound pvc-15c268c7-b507-11e6-932f-42010a800002 1Gi RWO 13h www-web-1 Bound pvc-15c79307-b507-11e6-932f-42010a800002 1Gi RWO 13h www-web-2 Bound pvc-e1125b27-b508-11e6-932f-42010a800002 1Gi RWO 13h www-web-3 Bound pvc-e1176df6-b508-11e6-932f-42010a800002 1Gi RWO 13h www-web-4 Bound pvc-e11bb5f8-b508-11e6-932f-42010a800002 1Gi RWO 13h </code></pre><p>五个 PersistentVolumeClaims 和五个 PersistentVolume 卷仍然存在。 查看 Pod 的<a href=#stable-storage>稳定存储</a>,你会发现当删除 StatefulSet 的 Pod 时,挂载到 StatefulSet 的 Pod 的 PersistentVolume 卷不会被删除。 当这种删除行为是由 StatefulSet 缩容引起时也是一样的。</p><h2 id=updating-statefulsets>更新 StatefulSet</h2><p>StatefulSet 控制器支持自动更新。 更新策略由 StatefulSet API 对象的 <code>spec.updateStrategy</code> 字段决定。这个特性能够用来更新一个 StatefulSet 中 Pod 的容器镜像、资源请求和限制、标签和注解。</p><p>有两个有效的更新策略:<code>RollingUpdate</code>(默认)和 <code>OnDelete</code>。</p><h3 id=rolling-update>滚动更新</h3><p><code>RollingUpdate</code> 更新策略会更新一个 StatefulSet 中的所有 Pod,采用与序号索引相反的顺序并遵循 StatefulSet 的保证。</p><p>你可以通过指定 <code>.spec.updateStrategy.rollingUpdate.partition</code> 将使用 <code>RollingUpdate</code> 策略的 StatefulSet 的更新拆分为多个<strong>分区</strong> 。你将在本教程中稍后练习此操作。</p><p>首先,尝试一个简单的滚动更新。</p><p>在一个终端窗口中对 <code>web</code> StatefulSet 执行 patch 操作来再次改变容器镜像:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl patch statefulset web --type<span style=color:#666>=</span><span style=color:#b44>'json'</span> -p<span style=color:#666>=</span><span style=color:#b44>'[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"registry.k8s.io/nginx-slim:0.24"}]'</span> </span></span></code></pre></div><pre tabindex=0><code>statefulset.apps/web patched </code></pre><p>在另一个终端监控 StatefulSet 中的 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 滚动完成后结束此 watch</span> </span></span><span style=display:flex><span><span style=color:#080;font-style:italic>#</span> </span></span><span style=display:flex><span><span style=color:#080;font-style:italic># 如果你不确定,请让它再运行一分钟</span> </span></span><span style=display:flex><span>kubectl get pod -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx --watch </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 7m web-1 1/1 Running 0 7m web-2 1/1 Running 0 8m web-2 1/1 Terminating 0 8m web-2 1/1 Terminating 0 8m web-2 0/1 Terminating 0 8m web-2 0/1 Terminating 0 8m web-2 0/1 Terminating 0 8m web-2 0/1 Terminating 0 8m web-2 0/1 Pending 0 0s web-2 0/1 Pending 0 0s web-2 0/1 ContainerCreating 0 0s web-2 1/1 Running 0 19s web-1 1/1 Terminating 0 8m web-1 0/1 Terminating 0 8m web-1 0/1 Terminating 0 8m web-1 0/1 Terminating 0 8m web-1 0/1 Pending 0 0s web-1 0/1 Pending 0 0s web-1 0/1 ContainerCreating 0 0s web-1 1/1 Running 0 6s web-0 1/1 Terminating 0 7m web-0 1/1 Terminating 0 7m web-0 0/1 Terminating 0 7m web-0 0/1 Terminating 0 7m web-0 0/1 Terminating 0 7m web-0 0/1 Terminating 0 7m web-0 0/1 Pending 0 0s web-0 0/1 Pending 0 0s web-0 0/1 ContainerCreating 0 0s web-0 1/1 Running 0 10s </code></pre><p>StatefulSet 里的 Pod 采用和序号相反的顺序更新。在更新下一个 Pod 前,StatefulSet 控制器终止每个 Pod 并等待它们变成 Running 和 Ready。 请注意,虽然在顺序后继者变成 Running 和 Ready 之前 StatefulSet 控制器不会更新下一个 Pod,但它仍然会重建任何在更新过程中发生故障的 Pod,使用的是它们现有的版本。</p><p>已经接收到更新请求的 Pod 将会被恢复为更新的版本,没有收到请求的 Pod 则会被恢复为之前的版本。 像这样,控制器尝试继续使应用保持健康并在出现间歇性故障时保持更新的一致性。</p><p>获取 Pod 来查看它们的容器镜像:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#a2f;font-weight:700>for</span> p in <span style=color:#666>0</span> <span style=color:#666>1</span> 2; <span style=color:#a2f;font-weight:700>do</span> kubectl get pod <span style=color:#b44>"web-</span><span style=color:#b8860b>$p</span><span style=color:#b44>"</span> --template <span style=color:#b44>'{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'</span>; echo; <span style=color:#a2f;font-weight:700>done</span> </span></span></code></pre></div><pre tabindex=0><code>registry.k8s.io/nginx-slim:0.24 registry.k8s.io/nginx-slim:0.24 registry.k8s.io/nginx-slim:0.24 </code></pre><p>StatefulSet 中的所有 Pod 现在都在运行之前的容器镜像。</p><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>你还可以使用 <code>kubectl rollout status sts/<名称></code> 来查看 StatefulSet 的滚动更新状态。</p></div><h4 id=staging-an-update>分段更新</h4><p>你可以通过指定 <code>.spec.updateStrategy.rollingUpdate.partition</code> 将使用 <code>RollingUpdate</code> 策略的 StatefulSet 的更新拆分为多个<strong>分区</strong> 。</p><p>有关更多上下文,你可以阅读 StatefulSet 概念页面中的<a href=/zh-cn/docs/concepts/workloads/controllers/statefulset/#partitions>分区滚动更新</a>。</p><p>你可以使用 <code>.spec.updateStrategy.rollingUpdate</code> 中的 <code>partition</code> 字段对 StatefulSet 执行更新的分段操作。 对于此更新,你将保持 StatefulSet 中现有 Pod 不变,同时更改 StatefulSet 的 Pod 模板。 然后,你(或通过教程之外的一些外部自动化工具)可以触发准备好的更新。</p><p>对 <code>web</code> StatefulSet 执行 Patch 操作,为 <code>updateStrategy</code> 字段添加一个分区:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># "partition" 的值决定更改适用于哪些序号</span> </span></span><span style=display:flex><span><span style=color:#080;font-style:italic># 确保使用比 StatefulSet 的最后一个序号更大的数字</span> </span></span><span style=display:flex><span>kubectl patch statefulset web -p <span style=color:#b44>'{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":3}}}}'</span> </span></span></code></pre></div><pre tabindex=0><code>statefulset.apps/web patched </code></pre><p>再次 Patch StatefulSet 来改变此 StatefulSet 使用的容器镜像:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl patch statefulset web --type<span style=color:#666>=</span><span style=color:#b44>'json'</span> -p<span style=color:#666>=</span><span style=color:#b44>'[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"registry.k8s.io/nginx-slim:0.21"}]'</span> </span></span></code></pre></div><pre tabindex=0><code>statefulset.apps/web patched </code></pre><p>删除 StatefulSet 中的 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete pod web-2 </span></span></code></pre></div><pre tabindex=0><code>pod "web-2" deleted </code></pre><p>等待替代的 Pod 变成 Running 和 Ready。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 当你看到 web-2 运行正常时结束 watch</span> </span></span><span style=display:flex><span>kubectl get pod -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx --watch </span></span></code></pre></div><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 4m web-1 1/1 Running 0 4m web-2 0/1 ContainerCreating 0 11s web-2 1/1 Running 0 18s </code></pre><p>获取 Pod 的容器镜像:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pod web-2 --template <span style=color:#b44>'{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'</span> </span></span></code></pre></div><pre tabindex=0><code>registry.k8s.io/nginx-slim:0.24 </code></pre><p>请注意,虽然更新策略是 <code>RollingUpdate</code>,StatefulSet 还是会使用原始的容器镜像恢复 Pod。 这是因为 Pod 的序号比 <code>updateStrategy</code> 指定的 <code>partition</code> 更小。</p><h4 id=rolling-out-a-canary>金丝雀发布</h4><p>现在,你将尝试对分段的变更进行<a href=https://glossary.cncf.io/canary-deployment/>金丝雀发布</a>。</p><p>你可以通过减少<a href=#staging-an-update>上文</a>指定的 <code>partition</code> 来进行金丝雀发布,以测试修改后的模板。</p><p>通过 patch 命令修改 StatefulSet 来减少分区:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># “partition” 的值应与 StatefulSet 现有的最高序号相匹配</span> </span></span><span style=display:flex><span>kubectl patch statefulset web -p <span style=color:#b44>'{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":2}}}}'</span> </span></span></code></pre></div><pre tabindex=0><code>statefulset.apps/web patched </code></pre><p>控制平面会触发 <code>web-2</code> 的替换(先优雅地 <strong>删除</strong> 现有 Pod,然后在删除完成后创建一个新的 Pod)。 等待新的 <code>web-2</code> Pod 变成 Running 和 Ready。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 这应该已经处于 Running 状态</span> </span></span><span style=display:flex><span>kubectl get pod -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx --watch </span></span></code></pre></div><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 4m web-1 1/1 Running 0 4m web-2 0/1 ContainerCreating 0 11s web-2 1/1 Running 0 18s </code></pre><p>获取 Pod 的容器:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pod web-2 --template <span style=color:#b44>'{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'</span> </span></span></code></pre></div><pre tabindex=0><code>registry.k8s.io/nginx-slim:0.21 </code></pre><p>当你改变 <code>partition</code> 时,StatefulSet 会自动更新 <code>web-2</code> Pod,这是因为 Pod 的序号大于或等于 <code>partition</code>。</p><p>删除 <code>web-1</code> Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete pod web-1 </span></span></code></pre></div><pre tabindex=0><code>pod "web-1" deleted </code></pre><p>等待 <code>web-1</code> 变成 Running 和 Ready。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 这应该已经处于 Running 状态</span> </span></span><span style=display:flex><span>kubectl get pod -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx --watch </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 6m web-1 0/1 Terminating 0 6m web-2 1/1 Running 0 2m web-1 0/1 Terminating 0 6m web-1 0/1 Terminating 0 6m web-1 0/1 Terminating 0 6m web-1 0/1 Pending 0 0s web-1 0/1 Pending 0 0s web-1 0/1 ContainerCreating 0 0s web-1 1/1 Running 0 18s </code></pre><p>获取 <code>web-1</code> Pod 的容器镜像:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pod web-1 --template <span style=color:#b44>'{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'</span> </span></span></code></pre></div><pre tabindex=0><code>registry.k8s.io/nginx-slim:0.24 </code></pre><p><code>web-1</code> 被按照原来的配置恢复,因为 Pod 的序号小于分区。当指定了分区时,如果更新了 StatefulSet 的 <code>.spec.template</code>,则所有序号大于或等于分区的 Pod 都将被更新。 如果一个序号小于分区的 Pod 被删除或者终止,它将被按照原来的配置恢复。</p><h4 id=phased-roll-outs>分阶段的发布</h4><p>你可以使用类似<a href=#rolling-out-a-canary>金丝雀发布</a>的方法执行一次分阶段的发布 (例如一次线性的、等比的或者指数形式的发布)。 要执行一次分阶段的发布,你需要设置 <code>partition</code> 为希望控制器暂停更新的序号。</p><p>分区当前为 <code>2</code>,请将其设置为 <code>0</code>:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl patch statefulset web -p <span style=color:#b44>'{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":0}}}}'</span> </span></span></code></pre></div><pre tabindex=0><code>statefulset.apps/web patched </code></pre><p>等待 StatefulSet 中的所有 Pod 变成 Running 和 Ready。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 这应该已经处于 Running 状态</span> </span></span><span style=display:flex><span>kubectl get pod -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx --watch </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 3m web-1 0/1 ContainerCreating 0 11s web-2 1/1 Running 0 2m web-1 1/1 Running 0 18s web-0 1/1 Terminating 0 3m web-0 1/1 Terminating 0 3m web-0 0/1 Terminating 0 3m web-0 0/1 Terminating 0 3m web-0 0/1 Terminating 0 3m web-0 0/1 Terminating 0 3m web-0 0/1 Pending 0 0s web-0 0/1 Pending 0 0s web-0 0/1 ContainerCreating 0 0s web-0 1/1 Running 0 3s </code></pre><p>获取 StatefulSet 中 Pod 的容器镜像详细信息:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#a2f;font-weight:700>for</span> p in <span style=color:#666>0</span> <span style=color:#666>1</span> 2; <span style=color:#a2f;font-weight:700>do</span> kubectl get pod <span style=color:#b44>"web-</span><span style=color:#b8860b>$p</span><span style=color:#b44>"</span> --template <span style=color:#b44>'{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'</span>; echo; <span style=color:#a2f;font-weight:700>done</span> </span></span></code></pre></div><pre tabindex=0><code>registry.k8s.io/nginx-slim:0.21 registry.k8s.io/nginx-slim:0.21 registry.k8s.io/nginx-slim:0.21 </code></pre><p>将 <code>partition</code> 改变为 <code>0</code> 以允许 StatefulSet 继续更新过程。</p><h3 id=on-delete>OnDelete 策略</h3><p>通过将 <code>.spec.template.updateStrategy.type</code> 设置为 <code>OnDelete</code>,你可以为 StatefulSet 选择此更新策略。</p><p>对 <code>web</code> StatefulSet 执行 patch 操作,以使用 <code>OnDelete</code> 更新策略:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl patch statefulset web -p <span style=color:#b44>'{"spec":{"updateStrategy":{"type":"OnDelete"}}}'</span> </span></span></code></pre></div><pre tabindex=0><code>statefulset.apps/web patched </code></pre><p>当你选择这个更新策略并修改 StatefulSet 的 <code>.spec.template</code> 字段时,StatefulSet 控制器将不会自动更新 Pod。 你需要自己手动管理发布,或使用单独的自动化工具来管理发布。</p><h2 id=deleting-statefulsets>删除 StatefulSet</h2><p>StatefulSet 同时支持<strong>非级联</strong>和<strong>级联</strong>删除。使用非级联方式<strong>删除</strong> StatefulSet 时,StatefulSet 的 Pod 不会被删除。使用级联<strong>删除</strong>时,StatefulSet 和它的 Pod 都会被删除。</p><p>阅读<a href=/zh-cn/docs/tasks/administer-cluster/use-cascading-deletion/>在集群中使用级联删除</a>, 以了解通用的级联删除。</p><h3 id=non-cascading-delete>非级联删除</h3><p>在一个终端窗口监视 StatefulSet 中的 Pod。</p><pre tabindex=0><code># 当 StatefulSet 没有 Pod 时结束此 watch kubectl get pods --watch -l app=nginx </code></pre><p>使用 <a href=/docs/reference/generated/kubectl/kubectl-commands/#delete><code>kubectl delete</code></a> 删除 StatefulSet。请确保提供了 <code>--cascade=orphan</code> 参数给命令。这个参数告诉 Kubernetes 只删除 StatefulSet 而<strong>不要</strong>删除它的任何 Pod。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete statefulset web --cascade<span style=color:#666>=</span>orphan </span></span></code></pre></div><pre tabindex=0><code>statefulset.apps "web" deleted </code></pre><p>获取 Pod 来检查它们的状态:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx </span></span></code></pre></div><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 6m web-1 1/1 Running 0 7m web-2 1/1 Running 0 5m </code></pre><p>虽然 <code>web</code> 已经被删除了,但所有 Pod 仍然处于 Running 和 Ready 状态。 删除 <code>web-0</code>:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete pod web-0 </span></span></code></pre></div><pre tabindex=0><code>pod "web-0" deleted </code></pre><p>获取 StatefulSet 的 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx </span></span></code></pre></div><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE web-1 1/1 Running 0 10m web-2 1/1 Running 0 7m </code></pre><p>由于 <code>web</code> StatefulSet 已经被删除,<code>web-0</code> 没有被重新启动。</p><p>在一个终端监控 StatefulSet 的 Pod。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 让 watch 一直运行到你下次启动 watch 为止</span> </span></span><span style=display:flex><span>kubectl get pods --watch -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx </span></span></code></pre></div><p>在另一个终端里重新创建 StatefulSet。请注意,除非你删除了 <code>nginx</code> Service(你不应该这样做),你将会看到一个错误,提示 Service 已经存在。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/application/web/web.yaml </span></span></code></pre></div><pre tabindex=0><code>statefulset.apps/web created service/nginx unchanged </code></pre><p>请忽略这个错误。它仅表示 kubernetes 进行了一次创建 <strong>nginx</strong> Headless Service 的尝试,尽管那个 Service 已经存在。</p><p>在第一个终端中运行并检查 <code>kubectl get</code> 命令的输出。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 这应该已经处于 Running 状态</span> </span></span><span style=display:flex><span>kubectl get pods --watch -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx </span></span></code></pre></div><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE web-1 1/1 Running 0 16m web-2 1/1 Running 0 2m NAME READY STATUS RESTARTS AGE web-0 0/1 Pending 0 0s web-0 0/1 Pending 0 0s web-0 0/1 ContainerCreating 0 0s web-0 1/1 Running 0 18s web-2 1/1 Terminating 0 3m web-2 0/1 Terminating 0 3m web-2 0/1 Terminating 0 3m web-2 0/1 Terminating 0 3m </code></pre><p>当重新创建 <code>web</code> StatefulSet 时,<code>web-0</code> 被第一个重新启动。 由于 <code>web-1</code> 已经处于 Running 和 Ready 状态,当 <code>web-0</code> 变成 Running 和 Ready 时, StatefulSet 会接收这个 Pod。由于你重新创建的 StatefulSet 的 <code>replicas</code> 等于 2, 一旦 <code>web-0</code> 被重新创建并且 <code>web-1</code> 被认为已经处于 Running 和 Ready 状态时,<code>web-2</code> 将会被终止。</p><p>现在再看看被 Pod 的 Web 服务器加载的 <code>index.html</code> 的内容:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#a2f;font-weight:700>for</span> i in <span style=color:#666>0</span> 1; <span style=color:#a2f;font-weight:700>do</span> kubectl <span style=color:#a2f>exec</span> -i -t <span style=color:#b44>"web-</span><span style=color:#b8860b>$i</span><span style=color:#b44>"</span> -- curl http://localhost/; <span style=color:#a2f;font-weight:700>done</span> </span></span></code></pre></div><pre tabindex=0><code>web-0 web-1 </code></pre><p>尽管你同时删除了 StatefulSet 和 <code>web-0</code> Pod,但它仍然使用最初写入 <code>index.html</code> 文件的主机名进行服务。 这是因为 StatefulSet 永远不会删除和一个 Pod 相关联的 PersistentVolume 卷。 当你重建这个 StatefulSet 并且重新启动了 <code>web-0</code> 时,它原本的 PersistentVolume 卷会被重新挂载。</p><h3 id=cascading-delete>级联删除</h3><p>在一个终端窗口监视 StatefulSet 里的 Pod。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 让它运行直到下一页部分</span> </span></span><span style=display:flex><span>kubectl get pods --watch -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx </span></span></code></pre></div><p>在另一个窗口中再次删除这个 StatefulSet,这次省略 <code>--cascade=orphan</code> 参数。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete statefulset web </span></span></code></pre></div><pre tabindex=0><code>statefulset.apps "web" deleted </code></pre><p>在第一个终端检查 <code>kubectl get</code> 命令的输出,并等待所有的 Pod 变成 Terminating 状态。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 这应该已经处于 Running 状态</span> </span></span><span style=display:flex><span>kubectl get pods --watch -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx </span></span></code></pre></div><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 11m web-1 1/1 Running 0 27m NAME READY STATUS RESTARTS AGE web-0 1/1 Terminating 0 12m web-1 1/1 Terminating 0 29m web-0 0/1 Terminating 0 12m web-0 0/1 Terminating 0 12m web-0 0/1 Terminating 0 12m web-1 0/1 Terminating 0 29m web-1 0/1 Terminating 0 29m web-1 0/1 Terminating 0 29m </code></pre><p>如同你在<a href=#scaling-down>缩容</a>章节看到的,这些 Pod 按照与其序号索引相反的顺序每次终止一个。 在终止一个 Pod 前,StatefulSet 控制器会等待 Pod 后继者被完全终止。</p><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>尽管级联删除会删除 StatefulSet 及其 Pod,但级联<strong>不会</strong>删除与 StatefulSet 关联的 Headless Service。你必须手动删除 <code>nginx</code> Service。</p></div><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete service nginx </span></span></code></pre></div><pre tabindex=0><code>service "nginx" deleted </code></pre><p>再一次重新创建 StatefulSet 和 Headless Service:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/application/web/web.yaml </span></span></code></pre></div><pre tabindex=0><code>service/nginx created statefulset.apps/web created </code></pre><p>当 StatefulSet 所有的 Pod 变成 Running 和 Ready 时,获取它们的 <code>index.html</code> 文件的内容:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#a2f;font-weight:700>for</span> i in <span style=color:#666>0</span> 1; <span style=color:#a2f;font-weight:700>do</span> kubectl <span style=color:#a2f>exec</span> -i -t <span style=color:#b44>"web-</span><span style=color:#b8860b>$i</span><span style=color:#b44>"</span> -- curl http://localhost/; <span style=color:#a2f;font-weight:700>done</span> </span></span></code></pre></div><pre tabindex=0><code>web-0 web-1 </code></pre><p>即使你已经删除了 StatefulSet 和它的全部 Pod,这些 Pod 将会被重新创建并挂载它们的 PersistentVolume 卷,并且 <code>web-0</code> 和 <code>web-1</code> 将继续使用它的主机名提供服务。</p><p>最后删除 <code>nginx</code> Service:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete service nginx </span></span></code></pre></div><pre tabindex=0><code>service "nginx" deleted </code></pre><p>并且删除 <code>web</code> StatefulSet:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete statefulset web </span></span></code></pre></div><pre tabindex=0><code>statefulset "web" deleted </code></pre><h2 id=pod-management-policy>Pod 管理策略</h2><p>对于某些分布式系统来说,StatefulSet 的顺序性保证是不必要和/或者不应该的。 这些系统仅仅要求唯一性和身份标志。</p><p>你可以指定 <a href=/zh-cn/docs/concepts/workloads/controllers/statefulset/#pod-management-policies>Pod 管理策略</a> 以避免这个严格的顺序; 你可以选择 <code>OrderedReady</code>(默认)或 <code>Parallel</code>。</p><h3 id=orderedready-pod-management>OrderedReady Pod 管理策略</h3><p><code>OrderedReady</code> Pod 管理策略是 StatefulSet 的默认选项。它告诉 StatefulSet 控制器遵循上文展示的顺序性保证。</p><p>当你的应用程序需要或期望变更(例如推出应用程序的新版本)按照 StatefulSet 提供的序号(Pod 编号)的严格顺序发生时,请使用此选项。 换句话说,如果你已经有了 Pod <code>app-0</code>、<code>app-1</code> 和 <code>app-2</code>,Kubernetes 将首先更新 <code>app-0</code> 并检查它。 一旦检查良好,Kubernetes 就会更新 <code>app-1</code>,最后更新 <code>app-2</code>。</p><p>如果你再添加两个 Pod,Kubernetes 将设置 <code>app-3</code> 并等待其正常运行,然后再部署 <code>app-4</code>。</p><p>因为这是默认设置,所以你已经在练习使用它,本教程不会让你再次执行类似的步骤。</p><h3 id=parallel-pod-management>Parallel Pod 管理策略</h3><p>另一种选择,<code>Parallel</code> Pod 管理策略告诉 StatefulSet 控制器并行的终止所有 Pod, 在启动或终止另一个 Pod 前,不必等待这些 Pod 变成 Running 和 Ready 或者完全终止状态。</p><p><code>Parallel</code> Pod 管理选项仅影响扩缩容操作的行为。 变更操作不受其影响;Kubernetes 仍然按顺序推出变更。 对于本教程,应用本身非常简单:它是一个告诉你其主机名的网络服务器(因为这是一个 StatefulSet,每个 Pod 的主机名都是不同的且可预测的)。</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/application/web/web-parallel.yaml download=application/web/web-parallel.yaml><code>application/web/web-parallel.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("application-web-web-parallel-yaml")' title="复制 application/web/web-parallel.yaml 到剪贴板"></img></div><div class=includecode id=application-web-web-parallel-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Service<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>port</span>:<span style=color:#bbb> </span><span style=color:#666>80</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>web<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>clusterIP</span>:<span style=color:#bbb> </span>None<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:#00f;font-weight:700>---</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>apps/v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>StatefulSet<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>web<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>serviceName</span>:<span style=color:#bbb> </span><span style=color:#b44>"nginx"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>podManagementPolicy</span>:<span style=color:#bbb> </span><span style=color:#b44>"Parallel"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>replicas</span>:<span style=color:#bbb> </span><span style=color:#666>2</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>matchLabels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>template</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>registry.k8s.io/nginx-slim:0.24<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>containerPort</span>:<span style=color:#bbb> </span><span style=color:#666>80</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>web<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumeMounts</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>www<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>mountPath</span>:<span style=color:#bbb> </span>/usr/share/nginx/html<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumeClaimTemplates</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>www<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>accessModes</span>:<span style=color:#bbb> </span>[<span style=color:#bbb> </span><span style=color:#b44>"ReadWriteOnce"</span><span style=color:#bbb> </span>]<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>resources</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>requests</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>storage</span>:<span style=color:#bbb> </span>1Gi<span style=color:#bbb> </span></span></span></code></pre></div></div></div><p>这份清单和你在上文下载的完全一样,只是 <code>web</code> StatefulSet 的 <code>.spec.podManagementPolicy</code> 设置成了 <code>Parallel</code>。</p><p>在一个终端窗口监视 StatefulSet 中的 Pod。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 让 watch 一直运行直到本节结束</span> </span></span><span style=display:flex><span>kubectl get pod -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx --watch </span></span></code></pre></div><p>在另一个终端中,重新配置 StatefulSet 以进行 <code>Parallel</code> Pod 管理:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/application/web/web-parallel.yaml </span></span></code></pre></div><pre tabindex=0><code>service/nginx updated statefulset.apps/web updated </code></pre><p>保持你运行监视进程的终端为打开状态,并在另一个终端窗口中扩容 StatefulSet:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl scale statefulset/web --replicas<span style=color:#666>=</span><span style=color:#666>5</span> </span></span></code></pre></div><pre tabindex=0><code>statefulset.apps/web scaled </code></pre><p>在 <code>kubectl get</code> 命令运行的终端里检查它的输出。它可能看起来像:</p><pre tabindex=0><code>web-3 0/1 Pending 0 0s web-3 0/1 Pending 0 0s web-3 0/1 Pending 0 7s web-3 0/1 ContainerCreating 0 7s web-2 0/1 Pending 0 0s web-4 0/1 Pending 0 0s web-2 1/1 Running 0 8s web-4 0/1 ContainerCreating 0 4s web-3 1/1 Running 0 26s web-4 1/1 Running 0 2s </code></pre><p>StatefulSet 启动了三个新的 Pod,而且在启动第二和第三个之前并没有等待第一个变成 Running 和 Ready 状态。</p><p>如果你的工作负载具有有状态元素,或者需要 Pod 能够通过可预测的命名来相互识别, 特别是当你有时需要快速提供更多容量时,此方法非常有用。 如果本教程的这个简单 Web 服务突然每分钟收到额外 1,000,000 个请求, 那么你可能会想要运行更多 Pod,但你也不想等待每个新 Pod 启动。 并行启动额外的 Pod 可以缩短请求额外容量和使其可供使用之间的时间。</p><h2 id=清理现场>清理现场</h2><p>你应该打开两个终端,准备在清理过程中运行 <code>kubectl</code> 命令。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete sts web </span></span><span style=display:flex><span><span style=color:#080;font-style:italic># sts is an abbreviation for statefulset</span> </span></span></code></pre></div><p>你可以监视 <code>kubectl get</code> 来查看那些 Pod 被删除:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 当你看到需要的内容后结束 watch</span> </span></span><span style=display:flex><span>kubectl get pod -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>nginx --watch </span></span></code></pre></div><pre tabindex=0><code>web-3 1/1 Terminating 0 9m web-2 1/1 Terminating 0 9m web-3 1/1 Terminating 0 9m web-2 1/1 Terminating 0 9m web-1 1/1 Terminating 0 44m web-0 1/1 Terminating 0 44m web-0 0/1 Terminating 0 44m web-3 0/1 Terminating 0 9m web-2 0/1 Terminating 0 9m web-1 0/1 Terminating 0 44m web-0 0/1 Terminating 0 44m web-2 0/1 Terminating 0 9m web-2 0/1 Terminating 0 9m web-2 0/1 Terminating 0 9m web-1 0/1 Terminating 0 44m web-1 0/1 Terminating 0 44m web-1 0/1 Terminating 0 44m web-0 0/1 Terminating 0 44m web-0 0/1 Terminating 0 44m web-0 0/1 Terminating 0 44m web-3 0/1 Terminating 0 9m web-3 0/1 Terminating 0 9m web-3 0/1 Terminating 0 9m </code></pre><p>在删除过程中,StatefulSet 将并发的删除所有 Pod,在删除一个 Pod 前不会等待它的顺序后继者终止。</p><p>关闭 <code>kubectl get</code> 命令运行的终端并删除 <code>nginx</code> Service:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete svc nginx </span></span></code></pre></div><p>删除本教程中用到的 PersistentVolume 卷的持久化存储介质:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pvc </span></span></code></pre></div><pre tabindex=0><code>NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE www-web-0 Bound pvc-2bf00408-d366-4a12-bad0-1869c65d0bee 1Gi RWO standard 25m www-web-1 Bound pvc-ba3bfe9c-413e-4b95-a2c0-3ea8a54dbab4 1Gi RWO standard 24m www-web-2 Bound pvc-cba6cfa6-3a47-486b-a138-db5930207eaf 1Gi RWO standard 15m www-web-3 Bound pvc-0c04d7f0-787a-4977-8da3-d9d3a6d8d752 1Gi RWO standard 15m www-web-4 Bound pvc-b2c73489-e70b-4a4e-9ec1-9eab439aa43e 1Gi RWO standard 14m </code></pre><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pv </span></span></code></pre></div><pre tabindex=0><code>NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pvc-0c04d7f0-787a-4977-8da3-d9d3a6d8d752 1Gi RWO Delete Bound default/www-web-3 standard 15m pvc-2bf00408-d366-4a12-bad0-1869c65d0bee 1Gi RWO Delete Bound default/www-web-0 standard 25m pvc-b2c73489-e70b-4a4e-9ec1-9eab439aa43e 1Gi RWO Delete Bound default/www-web-4 standard 14m pvc-ba3bfe9c-413e-4b95-a2c0-3ea8a54dbab4 1Gi RWO Delete Bound default/www-web-1 standard 24m pvc-cba6cfa6-3a47-486b-a138-db5930207eaf 1Gi RWO Delete Bound default/www-web-2 standard 15m </code></pre><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete pvc www-web-0 www-web-1 www-web-2 www-web-3 www-web-4 </span></span></code></pre></div><pre tabindex=0><code>persistentvolumeclaim "www-web-0" deleted persistentvolumeclaim "www-web-1" deleted persistentvolumeclaim "www-web-2" deleted persistentvolumeclaim "www-web-3" deleted persistentvolumeclaim "www-web-4" deleted </code></pre><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pvc </span></span></code></pre></div><pre tabindex=0><code>No resources found in default namespace. </code></pre><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>你需要删除本教程中用到的 PersistentVolume 卷的持久化存储介质。</p><p>基于你的环境、存储配置和制备方式,按照必需的步骤保证回收所有的存储。</p></div></div><div class=td-content style=page-break-before:always><h1 id=pg-27580b3f65f3c2da07fc0f83be69da75>6.2 - 示例:使用持久卷部署 WordPress 和 MySQL</h1><p>本示例描述了如何通过 Minikube 在 Kubernetes 上安装 WordPress 和 MySQL。 这两个应用都使用 PersistentVolumes 和 PersistentVolumeClaims 保存数据。</p><p><a href=/zh-cn/docs/concepts/storage/persistent-volumes/>PersistentVolume</a>(PV)是在集群里由管理员手动制备或 Kubernetes 通过 <a href=/zh-cn/docs/concepts/storage/storage-classes>StorageClass</a> 动态制备的一块存储。 <a href=/zh-cn/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims>PersistentVolumeClaim</a> 是用户对存储的请求,该请求可由某个 PV 来满足。 PersistentVolumes 和 PersistentVolumeClaims 独立于 Pod 生命周期而存在, 在 Pod 重启、重新调度甚至删除过程中用于保存数据。</p><div class="alert alert-danger" role=alert><h4 class=alert-heading>警告:</h4><p>这种部署并不适合生产场景,因为它使用的是单实例 WordPress 和 MySQL Pod。 在生产场景中,请考虑使用 <a href=https://github.com/bitnami/charts/tree/master/bitnami/wordpress>WordPress Helm Chart</a> 部署 WordPress。</p></div><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>本教程中提供的文件使用 GA Deployment API,并且特定于 kubernetes 1.9 或更高版本。 如果你希望将本教程与 Kubernetes 的早期版本一起使用,请相应地更新 API 版本,或参考本教程的早期版本。</p></div><h2 id=教程目标>教程目标</h2><ul><li>创建 PersistentVolumeClaims 和 PersistentVolumes</li><li>创建 <code>kustomization.yaml</code> 以使用<ul><li>Secret 生成器</li><li>MySQL 资源配置</li><li>WordPress 资源配置</li></ul></li><li><code>kubectl apply -k ./</code> 来应用整个 kustomization 目录</li><li>清理</li></ul><h2 id=准备开始>准备开始</h2><p><p>你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 <a href=https://minikube.sigs.k8s.io/docs/tutorials/multi_node/>Minikube</a> 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:</p><ul><li><a href=https://killercoda.com/playgrounds/scenario/kubernetes>Killercoda</a></li><li><a href=https://labs.play-with-k8s.com/>玩转 Kubernetes</a></li></ul>要获知版本信息,请输入 <code>kubectl version</code>.</p><p>此例在 <code>kubectl</code> 1.27 或者更高版本有效。</p><p>下载下面的配置文件:</p><ol><li><p><a href=/examples/application/wordpress/mysql-deployment.yaml>mysql-deployment.yaml</a></p></li><li><p><a href=/examples/application/wordpress/wordpress-deployment.yaml>wordpress-deployment.yaml</a></p></li></ol><h2 id=创建-persistentvolumeclaims-和-persistentvolumes>创建 PersistentVolumeClaims 和 PersistentVolumes</h2><p>MySQL 和 Wordpress 都需要一个 PersistentVolume 来存储数据。 它们的 PersistentVolumeClaims 将在部署步骤中创建。</p><p>许多集群环境都安装了默认的 StorageClass。如果在 PersistentVolumeClaim 中未指定 StorageClass, 则使用集群的默认 StorageClass。</p><p>创建 PersistentVolumeClaim 时,将根据 StorageClass 配置动态制备一个 PersistentVolume。</p><div class="alert alert-danger" role=alert><h4 class=alert-heading>警告:</h4><p>在本地集群中,默认的 StorageClass 使用 <code>hostPath</code> 制备程序。<code>hostPath</code> 卷仅适用于开发和测试。 使用 <code>hostPath</code> 卷时,你的数据位于 Pod 调度到的节点上的 <code>/tmp</code> 中,并且不会在节点之间移动。 如果 Pod 死亡并被调度到集群中的另一个节点,或者该节点重新启动,则数据将丢失。</p></div><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>如果要建立需要使用 <code>hostPath</code> 制备程序的集群, 则必须在 <code>controller-manager</code> 组件中设置 <code>--enable-hostpath-provisioner</code> 标志。</p></div><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>如果你已经有运行在 Google Kubernetes Engine 的集群, 请参考<a href=https://cloud.google.com/kubernetes-engine/docs/tutorials/persistent-disk>此指南</a>。</p></div><h2 id=创建-kustomization-yaml>创建 kustomization.yaml</h2><h3 id=创建-secret-生成器>创建 Secret 生成器</h3><p><a href=/zh-cn/docs/concepts/configuration/secret/>Secret</a> 是存储诸如密码或密钥之类敏感数据的对象。 从 1.14 开始,<code>kubectl</code> 支持使用一个 kustomization 文件来管理 Kubernetes 对象。 你可以通过 <code>kustomization.yaml</code> 中的生成器创建一个 Secret。</p><p>通过以下命令在 <code>kustomization.yaml</code> 中添加一个 Secret 生成器。 你需要将 <code>YOUR_PASSWORD</code> 替换为自己要用的密码。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>cat <span style=color:#b44><<EOF >./kustomization.yaml </span></span></span><span style=display:flex><span><span style=color:#b44>secretGenerator: </span></span></span><span style=display:flex><span><span style=color:#b44>- name: mysql-pass </span></span></span><span style=display:flex><span><span style=color:#b44> literals: </span></span></span><span style=display:flex><span><span style=color:#b44> - password=YOUR_PASSWORD </span></span></span><span style=display:flex><span><span style=color:#b44>EOF</span> </span></span></code></pre></div><h2 id=补充-mysql-和-wordpress-的资源配置>补充 MySQL 和 WordPress 的资源配置</h2><p>以下清单文件描述的是一个单实例的 MySQL Deployment。MySQL 容器将 PersistentVolume 挂载在 <code>/var/lib/mysql</code>。 <code>MYSQL_ROOT_PASSWORD</code> 环境变量根据 Secret 设置数据库密码。</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/application/wordpress/mysql-deployment.yaml download=application/wordpress/mysql-deployment.yaml><code>application/wordpress/mysql-deployment.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("application-wordpress-mysql-deployment-yaml")' title="复制 application/wordpress/mysql-deployment.yaml 到剪贴板"></img></div><div class=includecode id=application-wordpress-mysql-deployment-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Service<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>wordpress-mysql<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>wordpress<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>port</span>:<span style=color:#bbb> </span><span style=color:#666>3306</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>wordpress<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>tier</span>:<span style=color:#bbb> </span>mysql<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>clusterIP</span>:<span style=color:#bbb> </span>None<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:#00f;font-weight:700>---</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>PersistentVolumeClaim<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>mysql-pv-claim<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>wordpress<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>accessModes</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- ReadWriteOnce<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>resources</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>requests</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>storage</span>:<span style=color:#bbb> </span>20Gi<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:#00f;font-weight:700>---</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>apps/v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Deployment<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>wordpress-mysql<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>wordpress<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>matchLabels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>wordpress<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>tier</span>:<span style=color:#bbb> </span>mysql<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>strategy</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>type</span>:<span style=color:#bbb> </span>Recreate<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>template</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>wordpress<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>tier</span>:<span style=color:#bbb> </span>mysql<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>mysql:8.0<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>mysql<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>env</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>MYSQL_ROOT_PASSWORD<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>valueFrom</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>secretKeyRef</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>mysql-pass<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>key</span>:<span style=color:#bbb> </span>password<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>MYSQL_DATABASE<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>value</span>:<span style=color:#bbb> </span>wordpress<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>MYSQL_USER<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>value</span>:<span style=color:#bbb> </span>wordpress<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>MYSQL_PASSWORD<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>valueFrom</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>secretKeyRef</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>mysql-pass<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>key</span>:<span style=color:#bbb> </span>password<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>containerPort</span>:<span style=color:#bbb> </span><span style=color:#666>3306</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>mysql<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumeMounts</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>mysql-persistent-storage<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>mountPath</span>:<span style=color:#bbb> </span>/var/lib/mysql<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumes</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>mysql-persistent-storage<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>persistentVolumeClaim</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>claimName</span>:<span style=color:#bbb> </span>mysql-pv-claim<span style=color:#bbb> </span></span></span></code></pre></div></div></div><p>以下清单文件描述的是一个单实例 WordPress Deployment。WordPress 容器将 PersistentVolume 挂载到 <code>/var/www/html</code>,用于保存网站数据文件。 <code>WORDPRESS_DB_HOST</code> 环境变量设置上面定义的 MySQL Service 的名称,WordPress 将通过 Service 访问数据库。 <code>WORDPRESS_DB_PASSWORD</code> 环境变量根据使用 kustomize 生成的 Secret 设置数据库密码。</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/application/wordpress/wordpress-deployment.yaml download=application/wordpress/wordpress-deployment.yaml><code>application/wordpress/wordpress-deployment.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("application-wordpress-wordpress-deployment-yaml")' title="复制 application/wordpress/wordpress-deployment.yaml 到剪贴板"></img></div><div class=includecode id=application-wordpress-wordpress-deployment-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Service<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>wordpress<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>wordpress<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>port</span>:<span style=color:#bbb> </span><span style=color:#666>80</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>wordpress<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>tier</span>:<span style=color:#bbb> </span>frontend<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>type</span>:<span style=color:#bbb> </span>LoadBalancer<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:#00f;font-weight:700>---</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>PersistentVolumeClaim<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>wp-pv-claim<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>wordpress<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>accessModes</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- ReadWriteOnce<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>resources</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>requests</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>storage</span>:<span style=color:#bbb> </span>20Gi<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:#00f;font-weight:700>---</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>apps/v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Deployment<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>wordpress<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>wordpress<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>matchLabels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>wordpress<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>tier</span>:<span style=color:#bbb> </span>frontend<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>strategy</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>type</span>:<span style=color:#bbb> </span>Recreate<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>template</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>wordpress<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>tier</span>:<span style=color:#bbb> </span>frontend<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>wordpress:6.2.1-apache<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>wordpress<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>env</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>WORDPRESS_DB_HOST<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>value</span>:<span style=color:#bbb> </span>wordpress-mysql<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>WORDPRESS_DB_PASSWORD<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>valueFrom</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>secretKeyRef</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>mysql-pass<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>key</span>:<span style=color:#bbb> </span>password<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>WORDPRESS_DB_USER<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>value</span>:<span style=color:#bbb> </span>wordpress<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>containerPort</span>:<span style=color:#bbb> </span><span style=color:#666>80</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>wordpress<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumeMounts</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>wordpress-persistent-storage<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>mountPath</span>:<span style=color:#bbb> </span>/var/www/html<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumes</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>wordpress-persistent-storage<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>persistentVolumeClaim</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>claimName</span>:<span style=color:#bbb> </span>wp-pv-claim<span style=color:#bbb> </span></span></span></code></pre></div></div></div><ol><li><p>下载 MySQL Deployment 配置文件。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>curl -LO https://k8s.io/examples/application/wordpress/mysql-deployment.yaml </span></span></code></pre></div></li></ol><ol start=2><li><p>下载 WordPress 配置文件。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>curl -LO https://k8s.io/examples/application/wordpress/wordpress-deployment.yaml </span></span></code></pre></div></li></ol><ol start=3><li><p>将上述内容追加到 <code>kustomization.yaml</code> 文件。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>cat <span style=color:#b44><<EOF >>./kustomization.yaml </span></span></span><span style=display:flex><span><span style=color:#b44>resources: </span></span></span><span style=display:flex><span><span style=color:#b44> - mysql-deployment.yaml </span></span></span><span style=display:flex><span><span style=color:#b44> - wordpress-deployment.yaml </span></span></span><span style=display:flex><span><span style=color:#b44>EOF</span> </span></span></code></pre></div></li></ol><h2 id=应用和验证>应用和验证</h2><p><code>kustomization.yaml</code> 包含用于部署 WordPress 网站以及 MySQL 数据库的所有资源。你可以通过以下方式应用目录:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -k ./ </span></span></code></pre></div><p>现在,你可以验证所有对象是否存在。</p><ol><li><p>通过运行以下命令验证 Secret 是否存在:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get secrets </span></span></code></pre></div><p>响应应如下所示:</p><pre tabindex=0><code>NAME TYPE DATA AGE mysql-pass-c57bb4t7mf Opaque 1 9s </code></pre></li></ol><ol start=2><li><p>验证是否已动态制备 PersistentVolume:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pvc </span></span></code></pre></div><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>制备和绑定 PV 可能要花费几分钟。</p></div><p>响应应如下所示:</p><pre tabindex=0><code>NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE mysql-pv-claim Bound pvc-8cbd7b2e-4044-11e9-b2bb-42010a800002 20Gi RWO standard 77s wp-pv-claim Bound pvc-8cd0df54-4044-11e9-b2bb-42010a800002 20Gi RWO standard 77s </code></pre></li></ol><ol start=3><li><p>通过运行以下命令来验证 Pod 是否正在运行:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods </span></span></code></pre></div><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>等待 Pod 状态变成 <code>RUNNING</code> 可能会花费几分钟。</p></div><p>响应应如下所示:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE wordpress-mysql-1894417608-x5dzt 1/1 Running 0 40s </code></pre></li></ol><ol start=4><li><p>通过运行以下命令来验证 Service 是否正在运行:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get services wordpress </span></span></code></pre></div><p>响应应如下所示:</p><pre tabindex=0><code>NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE wordpress LoadBalancer 10.0.0.89 <pending> 80:32406/TCP 4m </code></pre><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>Minikube 只能通过 NodePort 公开服务。EXTERNAL-IP 始终处于 pending 状态。</p></div></li></ol><ol start=5><li><p>运行以下命令以获取 WordPress 服务的 IP 地址:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>minikube service wordpress --url </span></span></code></pre></div><p>响应应如下所示:</p><pre tabindex=0><code>http://1.2.3.4:32406 </code></pre></li></ol><ol start=6><li><p>复制 IP 地址,然后将页面加载到浏览器中来查看你的站点。</p><p>你应该看到类似于以下屏幕截图的 WordPress 设置页面。</p><p><img alt=wordpress-init src=https://raw.githubusercontent.com/kubernetes/examples/master/mysql-wordpress-pd/WordPress.png></p><div class="alert alert-danger" role=alert><h4 class=alert-heading>警告:</h4><p>不要在此页面上保留 WordPress 安装。如果其他用户找到了它,他们可以在你的实例上建立一个网站并使用它来提供恶意内容。<br><br>通过创建用户名和密码来安装 WordPress 或删除你的实例。</p></div></li></ol><h2 id=清理现场>清理现场</h2><ol><li><p>运行以下命令删除你的 Secret、Deployment、Service 和 PersistentVolumeClaims:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete -k ./ </span></span></code></pre></div></li></ol><h2 id=接下来>接下来</h2><ul><li>进一步了解<a href=/zh-cn/docs/tasks/debug/debug-application/debug-running-pod/>自省与调试</a></li><li>进一步了解 <a href=/zh-cn/docs/concepts/workloads/controllers/job/>Job</a></li><li>进一步了解<a href=/zh-cn/docs/tasks/access-application-cluster/port-forward-access-application-cluster/>端口转发</a></li><li>了解如何<a href=/zh-cn/docs/tasks/debug/debug-application/get-shell-running-container/>获得容器的 Shell</a></li></ul></div><div class=td-content style=page-break-before:always><h1 id=pg-bf0d8e08fddd6e0282709b9fef8b5f67>6.3 - 示例:使用 StatefulSet 部署 Cassandra</h1><p>本教程描述了如何在 Kubernetes 上运行 <a href=https://cassandra.apache.org/>Apache Cassandra</a>。 数据库 Cassandra 需要永久性存储提供数据持久性(应用<strong>状态</strong>)。 在此示例中,自定义 Cassandra seed provider 使数据库在接入 Cassandra 集群时能够发现新的 Cassandra 实例。</p><p>使用<strong>StatefulSet</strong>可以更轻松地将有状态的应用程序部署到你的 Kubernetes 集群中。 有关本教程中使用的功能的更多信息, 请参阅 <a href=/zh-cn/docs/concepts/workloads/controllers/statefulset/>StatefulSet</a>。</p><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>Cassandra 和 Kubernetes 都使用术语<strong>节点</strong>来表示集群的成员。 在本教程中,属于 StatefulSet 的 Pod 是 Cassandra 节点,并且是 Cassandra 集群的成员(称为 <strong>ring</strong>)。 当这些 Pod 在你的 Kubernetes 集群中运行时,Kubernetes 控制平面会将这些 Pod 调度到 Kubernetes 的 <a class=glossary-tooltip title='Kubernetes 中的工作机器称作节点。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/architecture/nodes/ target=_blank aria-label=节点>节点</a>上。</p><p>当 Cassandra 节点启动时,使用 <strong>seed 列表</strong>来引导发现 ring 中的其他节点。 本教程部署了一个自定义的 Cassandra seed provider, 使数据库可以发现 Kubernetes 集群中出现的新的 Cassandra Pod。</p></div><h2 id=教程目标>教程目标</h2><ul><li>创建并验证 Cassandra 无头(headless)<a class=glossary-tooltip title='将运行在一组 Pods 上的应用程序公开为网络服务的抽象方法。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/services-networking/service/ target=_blank aria-label=Service>Service</a>。</li><li>使用 <a class=glossary-tooltip title='StatefulSet 用来管理某 Pod 集合的部署和扩缩,并为这些 Pod 提供持久存储和持久标识符。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/workloads/controllers/statefulset/ target=_blank aria-label=StatefulSet>StatefulSet</a> 创建一个 Cassandra ring。</li><li>验证 StatefulSet。</li><li>修改 StatefulSet。</li><li>删除 StatefulSet 及其 <a class=glossary-tooltip title='Pod 表示你的集群上一组正在运行的容器。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/workloads/pods/ target=_blank aria-label=Pod>Pod</a>。</li></ul><h2 id=准备开始>准备开始</h2><p>你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 <a href=https://minikube.sigs.k8s.io/docs/tutorials/multi_node/>Minikube</a> 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:</p><ul><li><a href=https://killercoda.com/playgrounds/scenario/kubernetes>Killercoda</a></li><li><a href=https://labs.play-with-k8s.com/>玩转 Kubernetes</a></li></ul><p>要完成本教程,你应该已经熟悉 <a class=glossary-tooltip title='Pod 表示你的集群上一组正在运行的容器。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/workloads/pods/ target=_blank aria-label=Pod>Pod</a>、 <a class=glossary-tooltip title='将运行在一组 Pods 上的应用程序公开为网络服务的抽象方法。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/services-networking/service/ target=_blank aria-label=Service>Service</a> 和 <a class=glossary-tooltip title='StatefulSet 用来管理某 Pod 集合的部署和扩缩,并为这些 Pod 提供持久存储和持久标识符。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/workloads/controllers/statefulset/ target=_blank aria-label=StatefulSet>StatefulSet</a>。</p><h3 id=额外的-minikube-设置说明>额外的 Minikube 设置说明</h3><div class="alert alert-caution" role=alert><h4 class=alert-heading>注意:</h4><p><a href=https://minikube.sigs.k8s.io/docs/>Minikube</a> 默认需要 2048MB 内存和 2 个 CPU。 在本教程中,使用默认资源配置运行 Minikube 会出现资源不足的错误。为避免这些错误,请使用以下设置启动 Minikube:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>minikube start --memory <span style=color:#666>5120</span> --cpus<span style=color:#666>=</span><span style=color:#666>4</span> </span></span></code></pre></div></div><h2 id=creating-a-cassandra-headless-service>为 Cassandra 创建无头(headless) Services</h2><p>在 Kubernetes 中,一个 <a class=glossary-tooltip title='将运行在一组 Pods 上的应用程序公开为网络服务的抽象方法。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/services-networking/service/ target=_blank aria-label=Service>Service</a> 描述了一组执行相同任务的 <a class=glossary-tooltip title='Pod 表示你的集群上一组正在运行的容器。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/workloads/pods/ target=_blank aria-label=Pod>Pod</a>。</p><p>以下 Service 用于在 Cassandra Pod 和集群中的客户端之间进行 DNS 查找:</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/application/cassandra/cassandra-service.yaml download=application/cassandra/cassandra-service.yaml><code>application/cassandra/cassandra-service.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("application-cassandra-cassandra-service-yaml")' title="复制 application/cassandra/cassandra-service.yaml 到剪贴板"></img></div><div class=includecode id=application-cassandra-cassandra-service-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Service<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>cassandra<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>cassandra<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>clusterIP</span>:<span style=color:#bbb> </span>None<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>port</span>:<span style=color:#bbb> </span><span style=color:#666>9042</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>cassandra<span style=color:#bbb> </span></span></span></code></pre></div></div></div><p>创建一个 Service 来跟踪 <code>cassandra-service.yaml</code> 文件中的所有 Cassandra StatefulSet:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/application/cassandra/cassandra-service.yaml </span></span></code></pre></div><h3 id=validating>验证(可选)</h3><p>获取 Cassandra Service。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get svc cassandra </span></span></code></pre></div><p>响应是:</p><pre tabindex=0><code>NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE cassandra ClusterIP None <none> 9042/TCP 45s </code></pre><p>如果没有看到名为 <code>cassandra</code> 的服务,则表示创建失败。 请阅读<a href=/zh-cn/docs/tasks/debug/debug-application/debug-service/>调试服务</a>,以解决常见问题。</p><h2 id=使用-statefulset-创建-cassandra-ring>使用 StatefulSet 创建 Cassandra Ring</h2><p>下面包含的 StatefulSet 清单创建了一个由三个 Pod 组成的 Cassandra ring。</p><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4>本示例使用 Minikube 的默认配置程序。 请为正在使用的云更新以下 StatefulSet。</div><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/application/cassandra/cassandra-statefulset.yaml download=application/cassandra/cassandra-statefulset.yaml><code>application/cassandra/cassandra-statefulset.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("application-cassandra-cassandra-statefulset-yaml")' title="复制 application/cassandra/cassandra-statefulset.yaml 到剪贴板"></img></div><div class=includecode id=application-cassandra-cassandra-statefulset-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>apps/v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>StatefulSet<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>cassandra<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>cassandra<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>serviceName</span>:<span style=color:#bbb> </span>cassandra<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>replicas</span>:<span style=color:#bbb> </span><span style=color:#666>3</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>matchLabels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>cassandra<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>template</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>cassandra<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>terminationGracePeriodSeconds</span>:<span style=color:#bbb> </span><span style=color:#666>500</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>cassandra<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>gcr.io/google-samples/cassandra:v13<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>imagePullPolicy</span>:<span style=color:#bbb> </span>Always<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>containerPort</span>:<span style=color:#bbb> </span><span style=color:#666>7000</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>intra-node<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>containerPort</span>:<span style=color:#bbb> </span><span style=color:#666>7001</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>tls-intra-node<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>containerPort</span>:<span style=color:#bbb> </span><span style=color:#666>7199</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>jmx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>containerPort</span>:<span style=color:#bbb> </span><span style=color:#666>9042</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>cql<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>resources</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>limits</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>cpu</span>:<span style=color:#bbb> </span><span style=color:#b44>"500m"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>memory</span>:<span style=color:#bbb> </span>1Gi<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>requests</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>cpu</span>:<span style=color:#bbb> </span><span style=color:#b44>"500m"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>memory</span>:<span style=color:#bbb> </span>1Gi<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>securityContext</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>capabilities</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>add</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- IPC_LOCK<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>lifecycle</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>preStop</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>exec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>command</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- /bin/sh<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- -c<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- nodetool drain<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>env</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>MAX_HEAP_SIZE<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>value</span>:<span style=color:#bbb> </span>512M<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>HEAP_NEWSIZE<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>value</span>:<span style=color:#bbb> </span>100M<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>CASSANDRA_SEEDS<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>value</span>:<span style=color:#bbb> </span><span style=color:#b44>"cassandra-0.cassandra.default.svc.cluster.local"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>CASSANDRA_CLUSTER_NAME<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>value</span>:<span style=color:#bbb> </span><span style=color:#b44>"K8Demo"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>CASSANDRA_DC<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>value</span>:<span style=color:#bbb> </span><span style=color:#b44>"DC1-K8Demo"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>CASSANDRA_RACK<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>value</span>:<span style=color:#bbb> </span><span style=color:#b44>"Rack1-K8Demo"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>POD_IP<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>valueFrom</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>fieldRef</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>fieldPath</span>:<span style=color:#bbb> </span>status.podIP<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>readinessProbe</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>exec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>command</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- /bin/bash<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- -c<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- /ready-probe.sh<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>initialDelaySeconds</span>:<span style=color:#bbb> </span><span style=color:#666>15</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>timeoutSeconds</span>:<span style=color:#bbb> </span><span style=color:#666>5</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:#080;font-style:italic># 这些卷挂载是持久的。它们类似内联申领,但并不完全相同,</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:#080;font-style:italic># 因为这些卷挂载的名称需要与 StatefulSet 中某 Pod 卷完全匹配。</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumeMounts</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>cassandra-data<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>mountPath</span>:<span style=color:#bbb> </span>/cassandra_data<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:#080;font-style:italic># 这些将被控制器转换为卷申领,并挂载在上述路径。</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:#080;font-style:italic># 请勿将此设置用于生产环境,除非使用了 GCEPersistentDisk 或其他 SSD 持久盘。</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumeClaimTemplates</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>cassandra-data<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>accessModes</span>:<span style=color:#bbb> </span>[<span style=color:#bbb> </span><span style=color:#b44>"ReadWriteOnce"</span><span style=color:#bbb> </span>]<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>storageClassName</span>:<span style=color:#bbb> </span>fast<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>resources</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>requests</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>storage</span>:<span style=color:#bbb> </span>1Gi<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:#00f;font-weight:700>---</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>StorageClass<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>storage.k8s.io/v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>fast<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>provisioner</span>:<span style=color:#bbb> </span>k8s.io/minikube-hostpath<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>parameters</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>type</span>:<span style=color:#bbb> </span>pd-ssd<span style=color:#bbb> </span></span></span></code></pre></div></div></div><p>使用 <code>cassandra-statefulset.yaml</code> 文件创建 Cassandra StatefulSet:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 如果你能未经修改地应用 cassandra-statefulset.yaml,请使用此命令</span> </span></span><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/application/cassandra/cassandra-statefulset.yaml </span></span></code></pre></div><p>如果你为了适合你的集群需要修改 <code>cassandra-statefulset.yaml</code>, 下载 <a href=https://k8s.io/examples/application/cassandra/cassandra-statefulset.yaml>https://k8s.io/examples/application/cassandra/cassandra-statefulset.yaml</a>, 然后应用修改后的清单。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 如果使用本地的 cassandra-statefulset.yaml ,请使用此命令</span> </span></span><span style=display:flex><span>kubectl apply -f cassandra-statefulset.yaml </span></span></code></pre></div><h2 id=验证-cassandra-statefulset>验证 Cassandra StatefulSet</h2><ol><li><p>获取 Cassandra StatefulSet:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get statefulset cassandra </span></span></code></pre></div><p>响应应该与此类似:</p><pre tabindex=0><code>NAME DESIRED CURRENT AGE cassandra 3 0 13s </code></pre><p><code>StatefulSet</code> 资源会按顺序部署 Pod。</p></li></ol><ol start=2><li><p>获取 Pod 查看已排序的创建状态:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods -l<span style=color:#666>=</span><span style=color:#b44>"app=cassandra"</span> </span></span></code></pre></div><p>响应应该与此类似:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE cassandra-0 1/1 Running 0 1m cassandra-1 0/1 ContainerCreating 0 8s </code></pre><p>这三个 Pod 要花几分钟的时间才能部署。部署之后,相同的命令将返回类似于以下的输出:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE cassandra-0 1/1 Running 0 10m cassandra-1 1/1 Running 0 9m cassandra-2 1/1 Running 0 8m </code></pre></li></ol><ol start=3><li><p>运行第一个 Pod 中的 Cassandra <a href=https://cwiki.apache.org/confluence/display/CASSANDRA2/NodeTool>nodetool</a>, 以显示 ring 的状态。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl <span style=color:#a2f>exec</span> -it cassandra-0 -- nodetool status </span></span></code></pre></div><p>响应应该与此类似:</p><pre tabindex=0><code>Datacenter: DC1-K8Demo ====================== Status=Up/Down |/ State=Normal/Leaving/Joining/Moving -- Address Load Tokens Owns (effective) Host ID Rack UN 172.17.0.5 83.57 KiB 32 74.0% e2dd09e6-d9d3-477e-96c5-45094c08db0f Rack1-K8Demo UN 172.17.0.4 101.04 KiB 32 58.8% f89d6835-3a42-4419-92b3-0e62cae1479c Rack1-K8Demo UN 172.17.0.6 84.74 KiB 32 67.1% a6a1e8c2-3dc5-4417-b1a0-26507af2aaad Rack1-K8Demo </code></pre></li></ol><h2 id=修改-cassandra-statefulset>修改 Cassandra StatefulSet</h2><p>使用 <code>kubectl edit</code> 修改 Cassandra StatefulSet 的大小。</p><ol><li><p>运行以下命令:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl edit statefulset cassandra </span></span></code></pre></div><p>此命令你的终端中打开一个编辑器。需要更改的是 <code>replicas</code> 字段。下面是 StatefulSet 文件的片段示例:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:#080;font-style:italic># 请编辑以下对象。以 '#' 开头的行将被忽略,</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:#080;font-style:italic># 且空文件将放弃编辑。如果保存此文件时发生错误,</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:#080;font-style:italic># 将重新打开并显示相关故障。</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>apps/v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>StatefulSet<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>creationTimestamp</span>:<span style=color:#bbb> </span>2016-08-13T18:40:58Z<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>generation</span>:<span style=color:#bbb> </span><span style=color:#666>1</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>cassandra<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>cassandra<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>namespace</span>:<span style=color:#bbb> </span>default<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>resourceVersion</span>:<span style=color:#bbb> </span><span style=color:#b44>"323"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>uid</span>:<span style=color:#bbb> </span>7a219483-6185-11e6-a910-42010a8a0fc0<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>replicas</span>:<span style=color:#bbb> </span><span style=color:#666>3</span><span style=color:#bbb> </span></span></span></code></pre></div></li></ol><ol start=2><li><p>将副本数(replicas)更改为 4,然后保存清单。</p><p>StatefulSet 现在可以扩展到运行 4 个 Pod。</p></li><li><p>获取 Cassandra StatefulSet 验证更改:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get statefulset cassandra </span></span></code></pre></div><p>响应应该与此类似:</p><pre tabindex=0><code>NAME DESIRED CURRENT AGE cassandra 4 4 36m </code></pre></li></ol><h2 id=清理现场>清理现场</h2><p>删除或缩小 StatefulSet 不会删除与 StatefulSet 关联的卷。 这个设置是出于安全考虑,因为你的数据比自动清除所有相关的 StatefulSet 资源更有价值。</p><div class="alert alert-danger" role=alert><h4 class=alert-heading>警告:</h4><p>根据存储类和回收策略,删除 <strong>PersistentVolumeClaims</strong> 可能导致关联的卷也被删除。 千万不要认为其容量声明被删除,你就能访问数据。</p></div><ol><li><p>运行以下命令(连在一起成为一个单独的命令)删除 Cassandra StatefulSet 中的所有内容:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#b8860b>grace</span><span style=color:#666>=</span><span style=color:#a2f;font-weight:700>$(</span>kubectl get pod cassandra-0 -o<span style=color:#666>=</span><span style=color:#b8860b>jsonpath</span><span style=color:#666>=</span><span style=color:#b44>'{.spec.terminationGracePeriodSeconds}'</span><span style=color:#a2f;font-weight:700>)</span> <span style=color:#b62;font-weight:700>\ </span></span></span><span style=display:flex><span><span style=color:#b62;font-weight:700></span> <span style=color:#666>&&</span> kubectl delete statefulset -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>cassandra <span style=color:#b62;font-weight:700>\ </span></span></span><span style=display:flex><span><span style=color:#b62;font-weight:700></span> <span style=color:#666>&&</span> <span style=color:#a2f>echo</span> <span style=color:#b44>"Sleeping </span><span style=color:#b68;font-weight:700>${</span><span style=color:#b8860b>grace</span><span style=color:#b68;font-weight:700>}</span><span style=color:#b44> seconds"</span> 1>&<span style=color:#666>2</span> <span style=color:#b62;font-weight:700>\ </span></span></span><span style=display:flex><span><span style=color:#b62;font-weight:700></span> <span style=color:#666>&&</span> sleep <span style=color:#b8860b>$grace</span> <span style=color:#b62;font-weight:700>\ </span></span></span><span style=display:flex><span><span style=color:#b62;font-weight:700></span> <span style=color:#666>&&</span> kubectl delete persistentvolumeclaim -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>cassandra </span></span></code></pre></div></li></ol><ol start=2><li><p>运行以下命令,删除你为 Cassandra 设置的 Service:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete service -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>cassandra </span></span></code></pre></div></li></ol><h2 id=cassandra-容器环境变量>Cassandra 容器环境变量</h2><p>本教程中的 Pod 使用来自 Google <a href=https://cloud.google.com/container-registry/docs/>容器镜像库</a> 的 <a href=https://github.com/kubernetes/examples/blob/master/cassandra/image/Dockerfile><code>gcr.io/google-samples/cassandra:v13</code></a> 镜像。上面的 Docker 镜像基于 <a href=https://github.com/kubernetes/release/tree/master/images/build/debian-base>debian-base</a>, 并且包含 OpenJDK 8。</p><p>该镜像包括来自 Apache Debian 存储库的标准 Cassandra 安装。 通过使用环境变量,你可以更改插入到 <code>cassandra.yaml</code> 中的值。</p><table><thead><tr><th>环境变量</th><th style=text-align:center>默认值</th></tr></thead><tbody><tr><td><code>CASSANDRA_CLUSTER_NAME</code></td><td style=text-align:center><code>'Test Cluster'</code></td></tr><tr><td><code>CASSANDRA_NUM_TOKENS</code></td><td style=text-align:center><code>32</code></td></tr><tr><td><code>CASSANDRA_RPC_ADDRESS</code></td><td style=text-align:center><code>0.0.0.0</code></td></tr></tbody></table><h2 id=接下来>接下来</h2><ul><li>了解如何<a href=/zh-cn/docs/tasks/run-application/scale-stateful-set/>扩缩 StatefulSet</a>。</li><li>了解有关 <a href=https://github.com/kubernetes/examples/blob/master/cassandra/java/src/main/java/io/k8s/cassandra/KubernetesSeedProvider.java><strong>KubernetesSeedProvider</strong></a> 的更多信息</li><li>查看更多自定义 <a href=https://git.k8s.io/examples/cassandra/java/README.md>Seed Provider Configurations</a></li></ul></div><div class=td-content style=page-break-before:always><h1 id=pg-4bfac214b5eb9ebddaf1f3811901d327>6.4 - 运行 ZooKeeper,一个分布式协调系统</h1><p>本教程展示了在 Kubernetes 上使用 <a href=/zh-cn/docs/concepts/workloads/controllers/statefulset/>StatefulSet</a>、 <a href=/zh-cn/docs/concepts/workloads/pods/disruptions/#pod-disruption-budget>PodDisruptionBudget</a> 和 <a href=/zh-cn/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity>PodAntiAffinity</a> 特性运行 <a href=https://zookeeper.apache.org>Apache Zookeeper</a>。</p><h2 id=准备开始>准备开始</h2><p>在开始本教程前,你应该熟悉以下 Kubernetes 概念。</p><ul><li><a href=/zh-cn/docs/concepts/workloads/pods/>Pods</a></li><li><a href=/zh-cn/docs/concepts/services-networking/dns-pod-service/>集群 DNS</a></li><li><a href=/zh-cn/docs/concepts/services-networking/service/#headless-services>无头服务(Headless Service)</a></li><li><a href=/zh-cn/docs/concepts/storage/persistent-volumes/>PersistentVolumes</a></li><li><a href=https://github.com/kubernetes/examples/tree/master/staging/persistent-volume-provisioning/>PersistentVolume 制备</a></li><li><a href=/zh-cn/docs/concepts/workloads/controllers/statefulset/>StatefulSet</a></li><li><a href=/zh-cn/docs/concepts/workloads/pods/disruptions/#pod-disruption-budget>PodDisruptionBudget</a></li><li><a href=/zh-cn/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity>PodAntiAffinity</a></li><li><a href=/zh-cn/docs/reference/kubectl/kubectl/>kubectl CLI</a></li></ul><p>你需要一个至少包含四个节点的集群,每个节点至少 2 个 CPU 和 4 GiB 内存。 在本教程中你将会隔离(Cordon)和腾空(Drain )集群的节点。 <strong>这意味着集群节点上所有的 Pod 将会被终止并移除。这些节点也会暂时变为不可调度</strong>。 在本教程中你应该使用一个独占的集群,或者保证你造成的干扰不会影响其它租户。</p><p>本教程假设你的集群已配置为动态制备 PersistentVolume。 如果你的集群没有配置成这样,在开始本教程前,你需要手动准备三个 20 GiB 的卷。</p><h2 id=教程目标>教程目标</h2><p>在学习本教程后,你将熟悉下列内容。</p><ul><li>如何使用 StatefulSet 部署一个 ZooKeeper ensemble。</li><li>如何一致地配置 ensemble。</li><li>如何在 ensemble 中分布 ZooKeeper 服务器的部署。</li><li>如何在计划维护中使用 PodDisruptionBudget 确保服务可用性。</li></ul><h3 id=zookeeper-basics>ZooKeeper</h3><p><a href=https://zookeeper.apache.org/doc/current/>Apache ZooKeeper</a> 是一个分布式的开源协调服务,用于分布式系统。 ZooKeeper 允许你读取、写入数据和发现数据更新。 数据按层次结构组织在文件系统中,并复制到 ensemble(一个 ZooKeeper 服务器的集合) 中所有的 ZooKeeper 服务器。对数据的所有操作都是原子的和顺序一致的。 ZooKeeper 通过 <a href=https://pdfs.semanticscholar.org/b02c/6b00bd5dbdbd951fddb00b906c82fa80f0b3.pdf>Zab</a> 一致性协议在 ensemble 的所有服务器之间复制一个状态机来确保这个特性。</p><p>Ensemble 使用 Zab 协议选举一个领导者,在选举出领导者前不能写入数据。 一旦选举出了领导者,ensemble 使用 Zab 保证所有写入被复制到一个 quorum, 然后这些写入操作才会被确认并对客户端可用。 如果没有遵照加权 quorums,一个 quorum 表示包含当前领导者的 ensemble 的多数成员。 例如,如果 ensemble 有 3 个服务器,一个包含领导者的成员和另一个服务器就组成了一个 quorum。 如果 ensemble 不能达成一个 quorum,数据将不能被写入。</p><p>ZooKeeper 在内存中保存它们的整个状态机,但是每个改变都被写入一个在存储介质上的持久 WAL(Write Ahead Log)。 当一个服务器出现故障时,它能够通过回放 WAL 恢复之前的状态。 为了防止 WAL 无限制的增长,ZooKeeper 服务器会定期的将内存状态快照保存到存储介质。 这些快照能够直接加载到内存中,所有在这个快照之前的 WAL 条目都可以被安全的丢弃。</p><h2 id=创建一个-zookeeper-ensemble>创建一个 ZooKeeper Ensemble</h2><p>下面的清单包含一个<a href=/zh-cn/docs/concepts/services-networking/service/#headless-services>无头服务</a>、 一个 <a href=/zh-cn/docs/concepts/services-networking/service/>Service</a>、 一个 <a href=/zh-cn/docs/concepts/workloads/pods/disruptions/#pod-disruption-budgets>PodDisruptionBudget</a> 和一个 <a href=/zh-cn/docs/concepts/workloads/controllers/statefulset/>StatefulSet</a>。</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/application/zookeeper/zookeeper.yaml download=application/zookeeper/zookeeper.yaml><code>application/zookeeper/zookeeper.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("application-zookeeper-zookeeper-yaml")' title="复制 application/zookeeper/zookeeper.yaml 到剪贴板"></img></div><div class=includecode id=application-zookeeper-zookeeper-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Service<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>zk-hs<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>zk<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>port</span>:<span style=color:#bbb> </span><span style=color:#666>2888</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>server<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>port</span>:<span style=color:#bbb> </span><span style=color:#666>3888</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>leader-election<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>clusterIP</span>:<span style=color:#bbb> </span>None<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>zk<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:#00f;font-weight:700>---</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Service<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>zk-cs<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>zk<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>port</span>:<span style=color:#bbb> </span><span style=color:#666>2181</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>client<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>zk<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:#00f;font-weight:700>---</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>policy/v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>PodDisruptionBudget<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>zk-pdb<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>matchLabels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>zk<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>maxUnavailable</span>:<span style=color:#bbb> </span><span style=color:#666>1</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:#00f;font-weight:700>---</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>apps/v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>StatefulSet<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>zk<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>matchLabels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>zk<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>serviceName</span>:<span style=color:#bbb> </span>zk-hs<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>replicas</span>:<span style=color:#bbb> </span><span style=color:#666>3</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>updateStrategy</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>type</span>:<span style=color:#bbb> </span>RollingUpdate<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>podManagementPolicy</span>:<span style=color:#bbb> </span>OrderedReady<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>template</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>zk<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>affinity</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>podAntiAffinity</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>requiredDuringSchedulingIgnoredDuringExecution</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>labelSelector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>matchExpressions</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>key</span>:<span style=color:#bbb> </span><span style=color:#b44>"app"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>operator</span>:<span style=color:#bbb> </span>In<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>values</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- zk<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>topologyKey</span>:<span style=color:#bbb> </span><span style=color:#b44>"kubernetes.io/hostname"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>kubernetes-zookeeper<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>imagePullPolicy</span>:<span style=color:#bbb> </span>Always<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span><span style=color:#b44>"registry.k8s.io/kubernetes-zookeeper:1.0-3.4.10"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>resources</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>requests</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>memory</span>:<span style=color:#bbb> </span><span style=color:#b44>"1Gi"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>cpu</span>:<span style=color:#bbb> </span><span style=color:#b44>"0.5"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>containerPort</span>:<span style=color:#bbb> </span><span style=color:#666>2181</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>client<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>containerPort</span>:<span style=color:#bbb> </span><span style=color:#666>2888</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>server<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>containerPort</span>:<span style=color:#bbb> </span><span style=color:#666>3888</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>leader-election<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>command</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- sh<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- -c<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:#b44>"start-zookeeper \ </span></span></span><span style=display:flex><span><span style=color:#b44> --servers=3 \ </span></span></span><span style=display:flex><span><span style=color:#b44> --data_dir=/var/lib/zookeeper/data \ </span></span></span><span style=display:flex><span><span style=color:#b44> --data_log_dir=/var/lib/zookeeper/data/log \ </span></span></span><span style=display:flex><span><span style=color:#b44> --conf_dir=/opt/zookeeper/conf \ </span></span></span><span style=display:flex><span><span style=color:#b44> --client_port=2181 \ </span></span></span><span style=display:flex><span><span style=color:#b44> --election_port=3888 \ </span></span></span><span style=display:flex><span><span style=color:#b44> --server_port=2888 \ </span></span></span><span style=display:flex><span><span style=color:#b44> --tick_time=2000 \ </span></span></span><span style=display:flex><span><span style=color:#b44> --init_limit=10 \ </span></span></span><span style=display:flex><span><span style=color:#b44> --sync_limit=5 \ </span></span></span><span style=display:flex><span><span style=color:#b44> --heap=512M \ </span></span></span><span style=display:flex><span><span style=color:#b44> --max_client_cnxns=60 \ </span></span></span><span style=display:flex><span><span style=color:#b44> --snap_retain_count=3 \ </span></span></span><span style=display:flex><span><span style=color:#b44> --purge_interval=12 \ </span></span></span><span style=display:flex><span><span style=color:#b44> --max_session_timeout=40000 \ </span></span></span><span style=display:flex><span><span style=color:#b44> --min_session_timeout=4000 \ </span></span></span><span style=display:flex><span><span style=color:#b44> --log_level=INFO"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>readinessProbe</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>exec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>command</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- sh<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- -c<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:#b44>"zookeeper-ready 2181"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>initialDelaySeconds</span>:<span style=color:#bbb> </span><span style=color:#666>10</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>timeoutSeconds</span>:<span style=color:#bbb> </span><span style=color:#666>5</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>livenessProbe</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>exec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>command</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- sh<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- -c<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:#b44>"zookeeper-ready 2181"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>initialDelaySeconds</span>:<span style=color:#bbb> </span><span style=color:#666>10</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>timeoutSeconds</span>:<span style=color:#bbb> </span><span style=color:#666>5</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumeMounts</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>datadir<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>mountPath</span>:<span style=color:#bbb> </span>/var/lib/zookeeper<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>securityContext</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>runAsUser</span>:<span style=color:#bbb> </span><span style=color:#666>1000</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>fsGroup</span>:<span style=color:#bbb> </span><span style=color:#666>1000</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumeClaimTemplates</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>datadir<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>accessModes</span>:<span style=color:#bbb> </span>[<span style=color:#bbb> </span><span style=color:#b44>"ReadWriteOnce"</span><span style=color:#bbb> </span>]<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>resources</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>requests</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>storage</span>:<span style=color:#bbb> </span>10Gi<span style=color:#bbb> </span></span></span></code></pre></div></div></div><p>打开一个命令行终端,使用命令 <a href=/docs/reference/generated/kubectl/kubectl-commands/#apply><code>kubectl apply</code></a> 创建这个清单。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/application/zookeeper/zookeeper.yaml </span></span></code></pre></div><p>这个操作创建了 <code>zk-hs</code> 无头服务、<code>zk-cs</code> 服务、<code>zk-pdb</code> PodDisruptionBudget 和 <code>zk</code> StatefulSet。</p><pre tabindex=0><code>service/zk-hs created service/zk-cs created poddisruptionbudget.policy/zk-pdb created statefulset.apps/zk created </code></pre><p>使用命令 <a href=/docs/reference/generated/kubectl/kubectl-commands/#get><code>kubectl get</code></a> 查看 StatefulSet 控制器创建的几个 Pod。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods -w -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>zk </span></span></code></pre></div><p>一旦 <code>zk-2</code> Pod 变成 Running 和 Ready 状态,请使用 <code>CRTL-C</code> 结束 kubectl。</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE zk-0 0/1 Pending 0 0s zk-0 0/1 Pending 0 0s zk-0 0/1 ContainerCreating 0 0s zk-0 0/1 Running 0 19s zk-0 1/1 Running 0 40s zk-1 0/1 Pending 0 0s zk-1 0/1 Pending 0 0s zk-1 0/1 ContainerCreating 0 0s zk-1 0/1 Running 0 18s zk-1 1/1 Running 0 40s zk-2 0/1 Pending 0 0s zk-2 0/1 Pending 0 0s zk-2 0/1 ContainerCreating 0 0s zk-2 0/1 Running 0 19s zk-2 1/1 Running 0 40s </code></pre><p>StatefulSet 控制器创建 3 个 Pod,每个 Pod 包含一个 <a href=https://archive.apache.org/dist/zookeeper/stable/>ZooKeeper</a> 服务容器。</p><h3 id=facilitating-leader-election>促成 Leader 选举</h3><p>由于在匿名网络中没有用于选举 leader 的终止算法,Zab 要求显式的进行成员关系配置, 以执行 leader 选举。Ensemble 中的每个服务器都需要具有一个独一无二的标识符, 所有的服务器均需要知道标识符的全集,并且每个标识符都需要和一个网络地址相关联。</p><p>使用命令 <a href=/docs/reference/generated/kubectl/kubectl-commands/#exec><code>kubectl exec</code></a> 获取 <code>zk</code> StatefulSet 中 Pod 的主机名。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#a2f;font-weight:700>for</span> i in <span style=color:#666>0</span> <span style=color:#666>1</span> 2; <span style=color:#a2f;font-weight:700>do</span> kubectl <span style=color:#a2f>exec</span> zk-<span style=color:#b8860b>$i</span> -- hostname; <span style=color:#a2f;font-weight:700>done</span> </span></span></code></pre></div><p>StatefulSet 控制器基于每个 Pod 的序号索引为它们各自提供一个唯一的主机名。 主机名采用 <code><statefulset 名称>-<序数索引></code> 的形式。 由于 <code>zk</code> StatefulSet 的 <code>replicas</code> 字段设置为 3,这个集合的控制器将创建 3 个 Pod,主机名为:<code>zk-0</code>、<code>zk-1</code> 和 <code>zk-2</code>。</p><pre tabindex=0><code>zk-0 zk-1 zk-2 </code></pre><p>ZooKeeper ensemble 中的服务器使用自然数作为唯一标识符, 每个服务器的标识符都保存在服务器的数据目录中一个名为 <code>myid</code> 的文件里。</p><p>检查每个服务器的 <code>myid</code> 文件的内容。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#a2f;font-weight:700>for</span> i in <span style=color:#666>0</span> <span style=color:#666>1</span> 2; <span style=color:#a2f;font-weight:700>do</span> <span style=color:#a2f>echo</span> <span style=color:#b44>"myid zk-</span><span style=color:#b8860b>$i</span><span style=color:#b44>"</span>;kubectl <span style=color:#a2f>exec</span> zk-<span style=color:#b8860b>$i</span> -- cat /var/lib/zookeeper/data/myid; <span style=color:#a2f;font-weight:700>done</span> </span></span></code></pre></div><p>由于标识符为自然数并且序号索引是非负整数,你可以在序号上加 1 来生成一个标识符。</p><pre tabindex=0><code>myid zk-0 1 myid zk-1 2 myid zk-2 3 </code></pre><p>获取 <code>zk</code> StatefulSet 中每个 Pod 的全限定域名(Fully Qualified Domain Name,FQDN)。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#a2f;font-weight:700>for</span> i in <span style=color:#666>0</span> <span style=color:#666>1</span> 2; <span style=color:#a2f;font-weight:700>do</span> kubectl <span style=color:#a2f>exec</span> zk-<span style=color:#b8860b>$i</span> -- hostname -f; <span style=color:#a2f;font-weight:700>done</span> </span></span></code></pre></div><p><code>zk-hs</code> Service 为所有 Pod 创建了一个域:<code>zk-hs.default.svc.cluster.local</code>。</p><pre tabindex=0><code>zk-0.zk-hs.default.svc.cluster.local zk-1.zk-hs.default.svc.cluster.local zk-2.zk-hs.default.svc.cluster.local </code></pre><p><a href=/zh-cn/docs/concepts/services-networking/dns-pod-service/>Kubernetes DNS</a> 中的 A 记录将 FQDN 解析成为 Pod 的 IP 地址。 如果 Kubernetes 重新调度这些 Pod,这个 A 记录将会使用这些 Pod 的新 IP 地址完成更新, 但 A 记录的名称不会改变。</p><p>ZooKeeper 在一个名为 <code>zoo.cfg</code> 的文件中保存它的应用配置。 使用 <code>kubectl exec</code> 在 <code>zk-0</code> Pod 中查看 <code>zoo.cfg</code> 文件的内容。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl <span style=color:#a2f>exec</span> zk-0 -- cat /opt/zookeeper/conf/zoo.cfg </span></span></code></pre></div><p>文件底部为 <code>server.1</code>、<code>server.2</code> 和 <code>server.3</code>,其中的 <code>1</code>、<code>2</code> 和 <code>3</code> 分别对应 ZooKeeper 服务器的 <code>myid</code> 文件中的标识符。 它们被设置为 <code>zk</code> StatefulSet 中的 Pods 的 FQDNs。</p><pre tabindex=0><code>clientPort=2181 dataDir=/var/lib/zookeeper/data dataLogDir=/var/lib/zookeeper/log tickTime=2000 initLimit=10 syncLimit=2000 maxClientCnxns=60 minSessionTimeout= 4000 maxSessionTimeout= 40000 autopurge.snapRetainCount=3 autopurge.purgeInterval=0 server.1=zk-0.zk-hs.default.svc.cluster.local:2888:3888 server.2=zk-1.zk-hs.default.svc.cluster.local:2888:3888 server.3=zk-2.zk-hs.default.svc.cluster.local:2888:3888 </code></pre><h3 id=achieving-consensus>达成共识</h3><p>一致性协议要求每个参与者的标识符唯一。 在 Zab 协议里任何两个参与者都不应该声明相同的唯一标识符。 对于让系统中的进程协商哪些进程已经提交了哪些数据而言,这是必须的。 如果有两个 Pod 使用相同的序号启动,这两个 ZooKeeper 服务器会将自己识别为相同的服务器。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods -w -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>zk </span></span></code></pre></div><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE zk-0 0/1 Pending 0 0s zk-0 0/1 Pending 0 0s zk-0 0/1 ContainerCreating 0 0s zk-0 0/1 Running 0 19s zk-0 1/1 Running 0 40s zk-1 0/1 Pending 0 0s zk-1 0/1 Pending 0 0s zk-1 0/1 ContainerCreating 0 0s zk-1 0/1 Running 0 18s zk-1 1/1 Running 0 40s zk-2 0/1 Pending 0 0s zk-2 0/1 Pending 0 0s zk-2 0/1 ContainerCreating 0 0s zk-2 0/1 Running 0 19s zk-2 1/1 Running 0 40s </code></pre><p>每个 Pod 的 A 记录仅在 Pod 变成 Ready 状态时被录入。 因此,ZooKeeper 服务器的 FQDN 只会解析到一个端点, 而那个端点将是申领其 <code>myid</code> 文件中所配置标识的唯一 ZooKeeper 服务器。</p><pre tabindex=0><code>zk-0.zk-hs.default.svc.cluster.local zk-1.zk-hs.default.svc.cluster.local zk-2.zk-hs.default.svc.cluster.local </code></pre><p>这保证了 ZooKeeper 的 <code>zoo.cfg</code> 文件中的 <code>servers</code> 属性代表了一个正确配置的 ensemble。</p><pre tabindex=0><code>server.1=zk-0.zk-hs.default.svc.cluster.local:2888:3888 server.2=zk-1.zk-hs.default.svc.cluster.local:2888:3888 server.3=zk-2.zk-hs.default.svc.cluster.local:2888:3888 </code></pre><p>当服务器使用 Zab 协议尝试提交一个值的时候,它们会达成一致并成功提交这个值 (如果领导者选举成功并且至少有两个 Pod 处于 Running 和 Ready 状态), 或者将会失败(如果没有满足上述条件中的任意一条)。 当一个服务器承认另一个服务器的代写时不会有状态产生。</p><h3 id=ensemble-健康检查>Ensemble 健康检查</h3><p>最基本的健康检查是向一个 ZooKeeper 服务器写入一些数据,然后从另一个服务器读取这些数据。</p><p>使用 <code>zkCli.sh</code> 脚本在 <code>zk-0</code> Pod 上写入 <code>world</code> 到路径 <code>/hello</code>。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl <span style=color:#a2f>exec</span> zk-0 zkCli.sh create /hello world </span></span></code></pre></div><pre tabindex=0><code>WATCHER:: WatchedEvent state:SyncConnected type:None path:null Created /hello </code></pre><p>使用下面的命令从 <code>zk-1</code> Pod 获取数据。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl <span style=color:#a2f>exec</span> zk-1 zkCli.sh get /hello </span></span></code></pre></div><p>你在 <code>zk-0</code> 上创建的数据在 ensemble 中所有的服务器上都是可用的。</p><pre tabindex=0><code>WATCHER:: WatchedEvent state:SyncConnected type:None path:null world cZxid = 0x100000002 ctime = Thu Dec 08 15:13:30 UTC 2016 mZxid = 0x100000002 mtime = Thu Dec 08 15:13:30 UTC 2016 pZxid = 0x100000002 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 5 numChildren = 0 </code></pre><h3 id=提供持久存储>提供持久存储</h3><p>如同在 <a href=#zookeeper-basics>ZooKeeper</a> 一节所提到的, ZooKeeper 提交所有的条目到一个持久 WAL,并周期性的将内存快照写入存储介质。 对于使用一致性协议实现一个复制状态机的应用来说, 使用 WAL 提供持久化是一种常用的技术,对于普通的存储应用也是如此。</p><p>使用 <a href=/docs/reference/generated/kubectl/kubectl-commands/#delete><code>kubectl delete</code></a> 删除 <code>zk</code> StatefulSet。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete statefulset zk </span></span></code></pre></div><pre tabindex=0><code>statefulset.apps "zk" deleted </code></pre><p>观察 StatefulSet 中的 Pod 变为终止状态。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods -w -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>zk </span></span></code></pre></div><p>当 <code>zk-0</code> 完全终止时,使用 <code>CRTL-C</code> 结束 kubectl。</p><pre tabindex=0><code>zk-2 1/1 Terminating 0 9m zk-0 1/1 Terminating 0 11m zk-1 1/1 Terminating 0 10m zk-2 0/1 Terminating 0 9m zk-2 0/1 Terminating 0 9m zk-2 0/1 Terminating 0 9m zk-1 0/1 Terminating 0 10m zk-1 0/1 Terminating 0 10m zk-1 0/1 Terminating 0 10m zk-0 0/1 Terminating 0 11m zk-0 0/1 Terminating 0 11m zk-0 0/1 Terminating 0 11m </code></pre><p>重新应用 <code>zookeeper.yaml</code> 中的清单。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f https://k8s.io/examples/application/zookeeper/zookeeper.yaml </span></span></code></pre></div><p><code>zk</code> StatefulSet 将会被创建。由于清单中的其他 API 对象已经存在,所以它们不会被修改。</p><p>观察 StatefulSet 控制器重建 StatefulSet 的 Pod。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods -w -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>zk </span></span></code></pre></div><p>一旦 <code>zk-2</code> Pod 处于 Running 和 Ready 状态,使用 <code>CRTL-C</code> 停止 kubectl 命令。</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE zk-0 0/1 Pending 0 0s zk-0 0/1 Pending 0 0s zk-0 0/1 ContainerCreating 0 0s zk-0 0/1 Running 0 19s zk-0 1/1 Running 0 40s zk-1 0/1 Pending 0 0s zk-1 0/1 Pending 0 0s zk-1 0/1 ContainerCreating 0 0s zk-1 0/1 Running 0 18s zk-1 1/1 Running 0 40s zk-2 0/1 Pending 0 0s zk-2 0/1 Pending 0 0s zk-2 0/1 ContainerCreating 0 0s zk-2 0/1 Running 0 19s zk-2 1/1 Running 0 40s </code></pre><p>从 <code>zk-2</code> Pod 中获取你在<a href=#Ensemble-%E5%81%A5%E5%BA%B7%E6%A3%80%E6%9F%A5>健康检查</a>中输入的值。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl <span style=color:#a2f>exec</span> zk-2 zkCli.sh get /hello </span></span></code></pre></div><p>尽管 <code>zk</code> StatefulSet 中所有的 Pod 都已经被终止并重建过, ensemble 仍然使用原来的数值提供服务。</p><pre tabindex=0><code>WATCHER:: WatchedEvent state:SyncConnected type:None path:null world cZxid = 0x100000002 ctime = Thu Dec 08 15:13:30 UTC 2016 mZxid = 0x100000002 mtime = Thu Dec 08 15:13:30 UTC 2016 pZxid = 0x100000002 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 5 numChildren = 0 </code></pre><p><code>zk</code> StatefulSet 的 <code>spec</code> 中的 <code>volumeClaimTemplates</code> 字段标识了将要为每个 Pod 准备的 PersistentVolume。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>volumeClaimTemplates</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>datadir<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>annotations</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volume.alpha.kubernetes.io/storage-class</span>:<span style=color:#bbb> </span>anything<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>accessModes</span>:<span style=color:#bbb> </span>[<span style=color:#bbb> </span><span style=color:#b44>"ReadWriteOnce"</span><span style=color:#bbb> </span>]<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>resources</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>requests</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>storage</span>:<span style=color:#bbb> </span>20Gi<span style=color:#bbb> </span></span></span></code></pre></div><p><code>StatefulSet</code> 控制器为 <code>StatefulSet</code> 中的每个 Pod 生成一个 <code>PersistentVolumeClaim</code>。</p><p>获取 <code>StatefulSet</code> 的 <code>PersistentVolumeClaim</code>。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pvc -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>zk </span></span></code></pre></div><p>当 <code>StatefulSet</code> 重新创建它的 Pod 时,Pod 的 PersistentVolume 会被重新挂载。</p><pre tabindex=0><code>NAME STATUS VOLUME CAPACITY ACCESSMODES AGE datadir-zk-0 Bound pvc-bed742cd-bcb1-11e6-994f-42010a800002 20Gi RWO 1h datadir-zk-1 Bound pvc-bedd27d2-bcb1-11e6-994f-42010a800002 20Gi RWO 1h datadir-zk-2 Bound pvc-bee0817e-bcb1-11e6-994f-42010a800002 20Gi RWO 1h </code></pre><p>StatefulSet 的容器 <code>template</code> 中的 <code>volumeMounts</code> 一节使得 PersistentVolume 被挂载到 ZooKeeper 服务器的数据目录。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>volumeMounts</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>datadir<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>mountPath</span>:<span style=color:#bbb> </span>/var/lib/zookeeper<span style=color:#bbb> </span></span></span></code></pre></div><p>当 <code>zk</code> <code>StatefulSet</code> 中的一个 Pod 被(重新)调度时,它总是拥有相同的 PersistentVolume, 挂载到 ZooKeeper 服务器的数据目录。 即使在 Pod 被重新调度时,所有对 ZooKeeper 服务器的 WAL 的写入和它们的全部快照都仍然是持久的。</p><h2 id=确保一致性配置>确保一致性配置</h2><p>如同在<a href=#facilitating-leader-election>促成领导者选举</a>和<a href=#achieving-consensus>达成一致</a> 小节中提到的,ZooKeeper ensemble 中的服务器需要一致性的配置来选举一个领导者并形成一个 quorum。它们还需要 Zab 协议的一致性配置来保证这个协议在网络中正确的工作。 在这次的示例中,我们通过直接将配置写入代码清单中来达到该目的。</p><p>获取 <code>zk</code> StatefulSet。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get sts zk -o yaml </span></span></code></pre></div><pre tabindex=0><code> ... command: - sh - -c - "start-zookeeper \ --servers=3 \ --data_dir=/var/lib/zookeeper/data \ --data_log_dir=/var/lib/zookeeper/data/log \ --conf_dir=/opt/zookeeper/conf \ --client_port=2181 \ --election_port=3888 \ --server_port=2888 \ --tick_time=2000 \ --init_limit=10 \ --sync_limit=5 \ --heap=512M \ --max_client_cnxns=60 \ --snap_retain_count=3 \ --purge_interval=12 \ --max_session_timeout=40000 \ --min_session_timeout=4000 \ --log_level=INFO" ... </code></pre><p>用于启动 ZooKeeper 服务器的命令将这些配置作为命令行参数传给了 ensemble。 你也可以通过环境变量来传入这些配置。</p><h3 id=configuring-logging>配置日志</h3><p><code>zkGenConfig.sh</code> 脚本产生的一个文件控制了 ZooKeeper 的日志行为。 ZooKeeper 使用了 <a href=https://logging.apache.org/log4j/2.x/>Log4j</a> 并默认使用基于文件大小和时间的滚动文件追加器作为日志配置。</p><p>从 <code>zk</code> StatefulSet 的一个 Pod 中获取日志配置。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl <span style=color:#a2f>exec</span> zk-0 cat /usr/etc/zookeeper/log4j.properties </span></span></code></pre></div><p>下面的日志配置会使 ZooKeeper 进程将其所有的日志写入标志输出文件流中。</p><pre tabindex=0><code>zookeeper.root.logger=CONSOLE zookeeper.console.threshold=INFO log4j.rootLogger=${zookeeper.root.logger} log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.Threshold=${zookeeper.console.threshold} log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n </code></pre><p>这是在容器里安全记录日志的最简单的方法。 由于应用的日志被写入标准输出,Kubernetes 将会为你处理日志轮转。 Kubernetes 还实现了一个智能保存策略, 保证写入标准输出和标准错误流的应用日志不会耗尽本地存储介质。</p><p>使用命令 <a href=/docs/reference/generated/kubectl/kubectl-commands/#logs><code>kubectl logs</code></a> 从一个 Pod 中取回最后 20 行日志。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl logs zk-0 --tail <span style=color:#666>20</span> </span></span></code></pre></div><p>使用 <code>kubectl logs</code> 或者从 Kubernetes Dashboard 可以查看写入到标准输出和标准错误流中的应用日志。</p><pre tabindex=0><code>2016-12-06 19:34:16,236 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52740 2016-12-06 19:34:16,237 [myid:1] - INFO [Thread-1136:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52740 (no session established for client) 2016-12-06 19:34:26,155 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52749 2016-12-06 19:34:26,155 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52749 2016-12-06 19:34:26,156 [myid:1] - INFO [Thread-1137:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52749 (no session established for client) 2016-12-06 19:34:26,222 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52750 2016-12-06 19:34:26,222 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52750 2016-12-06 19:34:26,226 [myid:1] - INFO [Thread-1138:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52750 (no session established for client) 2016-12-06 19:34:36,151 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52760 2016-12-06 19:34:36,152 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52760 2016-12-06 19:34:36,152 [myid:1] - INFO [Thread-1139:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52760 (no session established for client) 2016-12-06 19:34:36,230 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52761 2016-12-06 19:34:36,231 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52761 2016-12-06 19:34:36,231 [myid:1] - INFO [Thread-1140:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52761 (no session established for client) 2016-12-06 19:34:46,149 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52767 2016-12-06 19:34:46,149 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52767 2016-12-06 19:34:46,149 [myid:1] - INFO [Thread-1141:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52767 (no session established for client) 2016-12-06 19:34:46,230 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52768 2016-12-06 19:34:46,230 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52768 2016-12-06 19:34:46,230 [myid:1] - INFO [Thread-1142:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52768 (no session established for client) </code></pre><p>Kubernetes 支持与多种日志方案集成。 你可以选择一个最适合你的集群和应用的日志解决方案。 对于集群级别的日志输出与整合,可以考虑部署一个 <a href=/zh-cn/docs/concepts/cluster-administration/logging#sidecar-container-with-logging-agent>边车容器</a> 来轮转和提供日志数据。</p><h3 id=配置非特权用户>配置非特权用户</h3><p>在容器中允许应用以特权用户运行这条最佳实践是值得商讨的。 如果你的组织要求应用以非特权用户运行,你可以使用 <a href=/zh-cn/docs/tasks/configure-pod-container/security-context/>SecurityContext</a> 控制运行容器入口点所使用的用户。</p><p><code>zk</code> StatefulSet 的 Pod 的 <code>template</code> 包含了一个 <code>SecurityContext</code>。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>securityContext</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>runAsUser</span>:<span style=color:#bbb> </span><span style=color:#666>1000</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>fsGroup</span>:<span style=color:#bbb> </span><span style=color:#666>1000</span><span style=color:#bbb> </span></span></span></code></pre></div><p>在 Pod 的容器内部,UID 1000 对应用户 zookeeper,GID 1000 对应用户组 zookeeper。</p><p>从 <code>zk-0</code> Pod 获取 ZooKeeper 进程信息。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl <span style=color:#a2f>exec</span> zk-0 -- ps -elf </span></span></code></pre></div><p>由于 <code>securityContext</code> 对象的 <code>runAsUser</code> 字段被设置为 1000 而不是 root, ZooKeeper 进程将以 zookeeper 用户运行。</p><pre tabindex=0><code>F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD 4 S zookeep+ 1 0 0 80 0 - 1127 - 20:46 ? 00:00:00 sh -c zkGenConfig.sh && zkServer.sh start-foreground 0 S zookeep+ 27 1 0 80 0 - 1155556 - 20:46 ? 00:00:19 /usr/lib/jvm/java-8-openjdk-amd64/bin/java -Dzookeeper.log.dir=/var/log/zookeeper -Dzookeeper.root.logger=INFO,CONSOLE -cp /usr/bin/../build/classes:/usr/bin/../build/lib/*.jar:/usr/bin/../share/zookeeper/zookeeper-3.4.9.jar:/usr/bin/../share/zookeeper/slf4j-log4j12-1.6.1.jar:/usr/bin/../share/zookeeper/slf4j-api-1.6.1.jar:/usr/bin/../share/zookeeper/netty-3.10.5.Final.jar:/usr/bin/../share/zookeeper/log4j-1.2.16.jar:/usr/bin/../share/zookeeper/jline-0.9.94.jar:/usr/bin/../src/java/lib/*.jar:/usr/bin/../etc/zookeeper: -Xmx2G -Xms2G -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false org.apache.zookeeper.server.quorum.QuorumPeerMain /usr/bin/../etc/zookeeper/zoo.cfg </code></pre><p>默认情况下,当 Pod 的 PersistentVolume 被挂载到 ZooKeeper 服务器的数据目录时, 它只能被 root 用户访问。这个配置将阻止 ZooKeeper 进程写入它的 WAL 及保存快照。</p><p>在 <code>zk-0</code> Pod 上获取 ZooKeeper 数据目录的文件权限。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl <span style=color:#a2f>exec</span> -ti zk-0 -- ls -ld /var/lib/zookeeper/data </span></span></code></pre></div><p>由于 <code>securityContext</code> 对象的 <code>fsGroup</code> 字段设置为 1000, Pod 的 PersistentVolume 的所有权属于 zookeeper 用户组, 因而 ZooKeeper 进程能够成功地读写数据。</p><pre tabindex=0><code>drwxr-sr-x 3 zookeeper zookeeper 4096 Dec 5 20:45 /var/lib/zookeeper/data </code></pre><h2 id=管理-zookeeper-进程>管理 ZooKeeper 进程</h2><p><a href=https://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_supervision>ZooKeeper 文档</a> 指出 “你将需要一个监管程序用于管理每个 ZooKeeper 服务进程(JVM)”。 在分布式系统中,使用一个看门狗(监管程序)来重启故障进程是一种常用的模式。</p><h3 id=更新-ensemble>更新 Ensemble</h3><p><code>zk</code> <code>StatefulSet</code> 的更新策略被设置为了 <code>RollingUpdate</code>。</p><p>你可以使用 <code>kubectl patch</code> 更新分配给每个服务器的 <code>cpus</code> 的数量。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl patch sts zk --type<span style=color:#666>=</span><span style=color:#b44>'json'</span> -p<span style=color:#666>=</span><span style=color:#b44>'[{"op": "replace", "path": "/spec/template/spec/containers/0/resources/requests/cpu", "value":"0.3"}]'</span> </span></span></code></pre></div><pre tabindex=0><code>statefulset.apps/zk patched </code></pre><p>使用 <code>kubectl rollout status</code> 观测更新状态。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl rollout status sts/zk </span></span></code></pre></div><pre tabindex=0><code>waiting for statefulset rolling update to complete 0 pods at revision zk-5db4499664... Waiting for 1 pods to be ready... Waiting for 1 pods to be ready... waiting for statefulset rolling update to complete 1 pods at revision zk-5db4499664... Waiting for 1 pods to be ready... Waiting for 1 pods to be ready... waiting for statefulset rolling update to complete 2 pods at revision zk-5db4499664... Waiting for 1 pods to be ready... Waiting for 1 pods to be ready... statefulset rolling update complete 3 pods at revision zk-5db4499664... </code></pre><p>这项操作会逆序地依次终止每一个 Pod,并用新的配置重新创建。 这样做确保了在滚动更新的过程中 quorum 依旧保持工作。</p><p>使用 <code>kubectl rollout history</code> 命令查看历史或先前的配置。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl rollout <span style=color:#a2f>history</span> sts/zk </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>statefulsets "zk" REVISION 1 2 </code></pre><p>使用 <code>kubectl rollout undo</code> 命令撤销这次的改动。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl rollout undo sts/zk </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>statefulset.apps/zk rolled back </code></pre><h3 id=处理进程故障>处理进程故障</h3><p><a href=/zh-cn/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy>重启策略</a> 控制 Kubernetes 如何处理一个 Pod 中容器入口点的进程故障。 对于 StatefulSet 中的 Pod 来说,Always 是唯一合适的 RestartPolicy,也是默认值。 你应该<strong>绝不</strong>覆盖有状态应用的默认策略。</p><p>检查 <code>zk-0</code> Pod 中运行的 ZooKeeper 服务器的进程树。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl <span style=color:#a2f>exec</span> zk-0 -- ps -ef </span></span></code></pre></div><p>作为容器入口点的命令的 PID 为 1,Zookeeper 进程是入口点的子进程,PID 为 27。</p><pre tabindex=0><code>UID PID PPID C STIME TTY TIME CMD zookeep+ 1 0 0 15:03 ? 00:00:00 sh -c zkGenConfig.sh && zkServer.sh start-foreground zookeep+ 27 1 0 15:03 ? 00:00:03 /usr/lib/jvm/java-8-openjdk-amd64/bin/java -Dzookeeper.log.dir=/var/log/zookeeper -Dzookeeper.root.logger=INFO,CONSOLE -cp /usr/bin/../build/classes:/usr/bin/../build/lib/*.jar:/usr/bin/../share/zookeeper/zookeeper-3.4.9.jar:/usr/bin/../share/zookeeper/slf4j-log4j12-1.6.1.jar:/usr/bin/../share/zookeeper/slf4j-api-1.6.1.jar:/usr/bin/../share/zookeeper/netty-3.10.5.Final.jar:/usr/bin/../share/zookeeper/log4j-1.2.16.jar:/usr/bin/../share/zookeeper/jline-0.9.94.jar:/usr/bin/../src/java/lib/*.jar:/usr/bin/../etc/zookeeper: -Xmx2G -Xms2G -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false org.apache.zookeeper.server.quorum.QuorumPeerMain /usr/bin/../etc/zookeeper/zoo.cfg </code></pre><p>在一个终端观察 <code>zk</code> <code>StatefulSet</code> 中的 Pod。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pod -w -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>zk </span></span></code></pre></div><p>在另一个终端杀掉 Pod <code>zk-0</code> 中的 ZooKeeper 进程。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span> kubectl <span style=color:#a2f>exec</span> zk-0 -- pkill java </span></span></code></pre></div><p>ZooKeeper 进程的终结导致了它父进程的终止。由于容器的 <code>RestartPolicy</code> 是 Always,所以父进程被重启。</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE zk-0 1/1 Running 0 21m zk-1 1/1 Running 0 20m zk-2 1/1 Running 0 19m NAME READY STATUS RESTARTS AGE zk-0 0/1 Error 0 29m zk-0 0/1 Running 1 29m zk-0 1/1 Running 1 29m </code></pre><p>如果你的应用使用一个脚本(例如 <code>zkServer.sh</code>)来启动一个实现了应用业务逻辑的进程, 这个脚本必须和子进程一起结束。这保证了当实现应用业务逻辑的进程故障时, Kubernetes 会重启这个应用的容器。</p><h3 id=存活性测试>存活性测试</h3><p>你的应用配置为自动重启故障进程,但这对于保持一个分布式系统的健康来说是不够的。 许多场景下,一个系统进程可以是活动状态但不响应请求,或者是不健康状态。 你应该使用存活性探针来通知 Kubernetes 你的应用进程处于不健康状态,需要被重启。</p><p><code>zk</code> <code>StatefulSet</code> 的 Pod 的 <code>template</code> 一节指定了一个存活探针。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>livenessProbe</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>exec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>command</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- sh<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- -c<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:#b44>"zookeeper-ready 2181"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>initialDelaySeconds</span>:<span style=color:#bbb> </span><span style=color:#666>15</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>timeoutSeconds</span>:<span style=color:#bbb> </span><span style=color:#666>5</span><span style=color:#bbb> </span></span></span></code></pre></div><p>这个探针调用一个简单的 Bash 脚本,使用 ZooKeeper 的四字缩写 <code>ruok</code> 来测试服务器的健康状态。</p><pre tabindex=0><code>OK=$(echo ruok | nc 127.0.0.1 $1) if [ "$OK" == "imok" ]; then exit 0 else exit 1 fi </code></pre><p>在一个终端窗口中使用下面的命令观察 <code>zk</code> StatefulSet 中的 Pod。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pod -w -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>zk </span></span></code></pre></div><p>在另一个窗口中,从 Pod <code>zk-0</code> 的文件系统中删除 <code>zookeeper-ready</code> 脚本。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl <span style=color:#a2f>exec</span> zk-0 -- rm /opt/zookeeper/bin/zookeeper-ready </span></span></code></pre></div><p>当 ZooKeeper 进程的存活探针探测失败时,Kubernetes 将会为你自动重启这个进程, 从而保证 ensemble 中不健康状态的进程都被重启。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pod -w -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>zk </span></span></code></pre></div><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE zk-0 1/1 Running 0 1h zk-1 1/1 Running 0 1h zk-2 1/1 Running 0 1h NAME READY STATUS RESTARTS AGE zk-0 0/1 Running 0 1h zk-0 0/1 Running 1 1h zk-0 1/1 Running 1 1h </code></pre><h3 id=就绪性测试>就绪性测试</h3><p>就绪不同于存活。如果一个进程是存活的,它是可调度和健康的。 如果一个进程是就绪的,它应该能够处理输入。存活是就绪的必要非充分条件。 在许多场景下,特别是初始化和终止过程中,一个进程可以是存活但没有就绪的。</p><p>如果你指定了一个就绪探针,Kubernetes 将保证在就绪检查通过之前, 你的应用不会接收到网络流量。</p><p>对于一个 ZooKeeper 服务器来说,存活即就绪。 因此 <code>zookeeper.yaml</code> 清单中的就绪探针和存活探针完全相同。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>readinessProbe</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>exec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>command</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- sh<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- -c<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:#b44>"zookeeper-ready 2181"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>initialDelaySeconds</span>:<span style=color:#bbb> </span><span style=color:#666>15</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>timeoutSeconds</span>:<span style=color:#bbb> </span><span style=color:#666>5</span><span style=color:#bbb> </span></span></span></code></pre></div><p>虽然存活探针和就绪探针是相同的,但同时指定它们两者仍然重要。 这保证了 ZooKeeper ensemble 中只有健康的服务器能接收网络流量。</p><h2 id=tolerating-node-failure>容忍节点故障</h2><p>ZooKeeper 需要一个 quorum 来提交数据变动。对于一个拥有 3 个服务器的 ensemble 来说, 必须有两个服务器是健康的,写入才能成功。 在基于 quorum 的系统里,成员被部署在多个故障域中以保证可用性。 为了防止由于某台机器断连引起服务中断,最佳实践是防止应用的多个实例在相同的机器上共存。</p><p>默认情况下,Kubernetes 可以把 <code>StatefulSet</code> 的 Pod 部署在相同节点上。 对于你创建的 3 个服务器的 ensemble 来说, 如果有两个服务器并存于相同的节点上并且该节点发生故障时,ZooKeeper 服务将中断, 直至至少其中一个 Pod 被重新调度。</p><p>你应该总是提供多余的容量以允许关键系统进程在节点故障时能够被重新调度。 如果你这样做了,服务故障就只会持续到 Kubernetes 调度器重新调度某个 ZooKeeper 服务器为止。 但是,如果希望你的服务在容忍节点故障时无停服时间,你应该设置 <code>podAntiAffinity</code>。</p><p>使用下面的命令获取 <code>zk</code> <code>StatefulSet</code> 中的 Pod 的节点。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#a2f;font-weight:700>for</span> i in <span style=color:#666>0</span> <span style=color:#666>1</span> 2; <span style=color:#a2f;font-weight:700>do</span> kubectl get pod zk-<span style=color:#b8860b>$i</span> --template <span style=color:#666>{{</span>.spec.nodeName<span style=color:#666>}}</span>; <span style=color:#a2f>echo</span> <span style=color:#b44>""</span>; <span style=color:#a2f;font-weight:700>done</span> </span></span></code></pre></div><p><code>zk</code> <code>StatefulSet</code> 中所有的 Pod 都被部署在不同的节点。</p><pre tabindex=0><code>kubernetes-node-cxpk kubernetes-node-a5aq kubernetes-node-2g2d </code></pre><p>这是因为 <code>zk</code> <code>StatefulSet</code> 中的 Pod 指定了 <code>PodAntiAffinity</code>。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>affinity</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>podAntiAffinity</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>requiredDuringSchedulingIgnoredDuringExecution</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>labelSelector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>matchExpressions</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>key</span>:<span style=color:#bbb> </span><span style=color:#b44>"app"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>operator</span>:<span style=color:#bbb> </span>In<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>values</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- zk<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>topologyKey</span>:<span style=color:#bbb> </span><span style=color:#b44>"kubernetes.io/hostname"</span><span style=color:#bbb> </span></span></span></code></pre></div><p><code>requiredDuringSchedulingIgnoredDuringExecution</code> 告诉 Kubernetes 调度器, 在以 <code>topologyKey</code> 指定的域中,绝对不要把带有键为 <code>app</code>、值为 <code>zk</code> 的标签 的两个 Pod 调度到相同的节点。<code>topologyKey</code> <code>kubernetes.io/hostname</code> 表示 这个域是一个单独的节点。 使用不同的规则、标签和选择算符,你能够通过这种技术把你的 ensemble 分布 在不同的物理、网络和电力故障域之间。</p><h2 id=节点维护期间保持应用可用>节点维护期间保持应用可用</h2><p><strong>在本节中你将会隔离(Cordon)和腾空(Drain)节点。 如果你是在一个共享的集群里使用本教程,请保证不会影响到其他租户。</strong></p><p>上一小节展示了如何在节点之间分散 Pod 以在计划外的节点故障时保证服务存活。 但是你也需要为计划内维护引起的临时节点故障做准备。</p><p>使用此命令获取你的集群中的节点。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get nodes </span></span></code></pre></div><p>使用 <a href=/docs/reference/generated/kubectl/kubectl-commands/#cordon><code>kubectl cordon</code></a> 隔离你的集群中除 4 个节点以外的所有节点。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl cordon <node-name> </span></span></code></pre></div><p>使用下面的命令获取 <code>zk-pdb</code> <code>PodDisruptionBudget</code>。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pdb zk-pdb </span></span></code></pre></div><p><code>max-unavailable</code> 字段指示 Kubernetes 在任何时候,<code>zk</code> <code>StatefulSet</code> 至多有一个 Pod 是不可用的。</p><pre tabindex=0><code>NAME MIN-AVAILABLE MAX-UNAVAILABLE ALLOWED-DISRUPTIONS AGE zk-pdb N/A 1 1 </code></pre><p>在一个终端中,使用下面的命令观察 <code>zk</code> <code>StatefulSet</code> 中的 Pod。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods -w -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>zk </span></span></code></pre></div><p>在另一个终端中,使用下面的命令获取 Pod 当前调度的节点。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#a2f;font-weight:700>for</span> i in <span style=color:#666>0</span> <span style=color:#666>1</span> 2; <span style=color:#a2f;font-weight:700>do</span> kubectl get pod zk-<span style=color:#b8860b>$i</span> --template <span style=color:#666>{{</span>.spec.nodeName<span style=color:#666>}}</span>; <span style=color:#a2f>echo</span> <span style=color:#b44>""</span>; <span style=color:#a2f;font-weight:700>done</span> </span></span></code></pre></div><pre tabindex=0><code>kubernetes-node-pb41 kubernetes-node-ixsl kubernetes-node-i4c4 </code></pre><p>使用 <a href=/docs/reference/generated/kubectl/kubectl-commands/#drain><code>kubectl drain</code></a> 来隔离和腾空 <code>zk-0</code> Pod 调度所在的节点。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl drain <span style=color:#a2f;font-weight:700>$(</span>kubectl get pod zk-0 --template <span style=color:#666>{{</span>.spec.nodeName<span style=color:#666>}}</span><span style=color:#a2f;font-weight:700>)</span> --ignore-daemonsets --force --delete-emptydir-data </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>node "kubernetes-node-pb41" cordoned WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-pb41, kube-proxy-kubernetes-node-pb41; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-o5elz pod "zk-0" deleted node "kubernetes-node-pb41" drained </code></pre><p>由于你的集群中有 4 个节点, <code>kubectl drain</code> 执行成功,<code>zk-0</code> 被调度到其它节点。</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE zk-0 1/1 Running 2 1h zk-1 1/1 Running 0 1h zk-2 1/1 Running 0 1h NAME READY STATUS RESTARTS AGE zk-0 1/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Pending 0 0s zk-0 0/1 Pending 0 0s zk-0 0/1 ContainerCreating 0 0s zk-0 0/1 Running 0 51s zk-0 1/1 Running 0 1m </code></pre><p>在第一个终端中持续观察 <code>StatefulSet</code> 的 Pod 并腾空 <code>zk-1</code> 调度所在的节点。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl drain <span style=color:#a2f;font-weight:700>$(</span>kubectl get pod zk-1 --template <span style=color:#666>{{</span>.spec.nodeName<span style=color:#666>}}</span><span style=color:#a2f;font-weight:700>)</span> --ignore-daemonsets --force --delete-emptydir-data </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>kubernetes-node-ixsl" cordoned WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-ixsl, kube-proxy-kubernetes-node-ixsl; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-voc74 pod "zk-1" deleted node "kubernetes-node-ixsl" drained </code></pre><p><code>zk-1</code> Pod 不能被调度,这是因为 <code>zk</code> <code>StatefulSet</code> 包含了一个防止 Pod 共存的 <code>PodAntiAffinity</code> 规则,而且只有两个节点可用于调度, 这个 Pod 将保持在 Pending 状态。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods -w -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>zk </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE zk-0 1/1 Running 2 1h zk-1 1/1 Running 0 1h zk-2 1/1 Running 0 1h NAME READY STATUS RESTARTS AGE zk-0 1/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Pending 0 0s zk-0 0/1 Pending 0 0s zk-0 0/1 ContainerCreating 0 0s zk-0 0/1 Running 0 51s zk-0 1/1 Running 0 1m zk-1 1/1 Terminating 0 2h zk-1 0/1 Terminating 0 2h zk-1 0/1 Terminating 0 2h zk-1 0/1 Terminating 0 2h zk-1 0/1 Pending 0 0s zk-1 0/1 Pending 0 0s </code></pre><p>继续观察 StatefulSet 中的 Pod 并腾空 <code>zk-2</code> 调度所在的节点。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl drain <span style=color:#a2f;font-weight:700>$(</span>kubectl get pod zk-2 --template <span style=color:#666>{{</span>.spec.nodeName<span style=color:#666>}}</span><span style=color:#a2f;font-weight:700>)</span> --ignore-daemonsets --force --delete-emptydir-data </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>node "kubernetes-node-i4c4" cordoned WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-i4c4, kube-proxy-kubernetes-node-i4c4; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-dyrog WARNING: Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-dyrog; Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-i4c4, kube-proxy-kubernetes-node-i4c4 There are pending pods when an error occurred: Cannot evict pod as it would violate the pod's disruption budget. pod/zk-2 </code></pre><p>使用 <code>CTRL-C</code> 终止 kubectl。</p><p>你不能腾空第三个节点,因为驱逐 <code>zk-2</code> 将和 <code>zk-budget</code> 冲突。 然而这个节点仍然处于隔离状态(Cordoned)。</p><p>使用 <code>zkCli.sh</code> 从 <code>zk-0</code> 取回你的健康检查中输入的数值。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl <span style=color:#a2f>exec</span> zk-0 zkCli.sh get /hello </span></span></code></pre></div><p>由于遵守了 <code>PodDisruptionBudget</code>,服务仍然可用。</p><pre tabindex=0><code>WatchedEvent state:SyncConnected type:None path:null world cZxid = 0x200000002 ctime = Wed Dec 07 00:08:59 UTC 2016 mZxid = 0x200000002 mtime = Wed Dec 07 00:08:59 UTC 2016 pZxid = 0x200000002 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 5 numChildren = 0 </code></pre><p>使用 <a href=/docs/reference/generated/kubectl/kubectl-commands/#uncordon><code>kubectl uncordon</code></a> 来取消对第一个节点的隔离。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl uncordon kubernetes-node-pb41 </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>node "kubernetes-node-pb41" uncordoned </code></pre><p><code>zk-1</code> 被重新调度到了这个节点。等待 <code>zk-1</code> 变为 Running 和 Ready 状态。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods -w -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>zk </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE zk-0 1/1 Running 2 1h zk-1 1/1 Running 0 1h zk-2 1/1 Running 0 1h NAME READY STATUS RESTARTS AGE zk-0 1/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Terminating 2 2h zk-0 0/1 Pending 0 0s zk-0 0/1 Pending 0 0s zk-0 0/1 ContainerCreating 0 0s zk-0 0/1 Running 0 51s zk-0 1/1 Running 0 1m zk-1 1/1 Terminating 0 2h zk-1 0/1 Terminating 0 2h zk-1 0/1 Terminating 0 2h zk-1 0/1 Terminating 0 2h zk-1 0/1 Pending 0 0s zk-1 0/1 Pending 0 0s zk-1 0/1 Pending 0 12m zk-1 0/1 ContainerCreating 0 12m zk-1 0/1 Running 0 13m zk-1 1/1 Running 0 13m </code></pre><p>尝试腾空 <code>zk-2</code> 调度所在的节点。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl drain <span style=color:#a2f;font-weight:700>$(</span>kubectl get pod zk-2 --template <span style=color:#666>{{</span>.spec.nodeName<span style=color:#666>}}</span><span style=color:#a2f;font-weight:700>)</span> --ignore-daemonsets --force --delete-emptydir-data </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>node "kubernetes-node-i4c4" already cordoned WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-i4c4, kube-proxy-kubernetes-node-i4c4; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-dyrog pod "heapster-v1.2.0-2604621511-wht1r" deleted pod "zk-2" deleted node "kubernetes-node-i4c4" drained </code></pre><p>这次 <code>kubectl drain</code> 执行成功。</p><p>取消第二个节点的隔离,以允许 <code>zk-2</code> 被重新调度。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl uncordon kubernetes-node-ixsl </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>node "kubernetes-node-ixsl" uncordoned </code></pre><p>你可以同时使用 <code>kubectl drain</code> 和 <code>PodDisruptionBudgets</code> 来保证你的服务在维护过程中仍然可用。 如果使用了腾空操作来隔离节点并在节点离线之前驱逐了 Pod, 那么设置了干扰预算的服务将会遵守该预算。 你应该总是为关键服务分配额外容量,这样它们的 Pod 就能够迅速的重新调度。</p><h2 id=清理现场>清理现场</h2><ul><li>使用 <code>kubectl uncordon</code> 解除你集群中所有节点的隔离。</li><li>你需要删除在本教程中使用的 PersistentVolume 的持久存储介质。 请遵循必须的步骤,基于你的环境、存储配置和制备方法,保证回收所有的存储。</li></ul></div><div class=td-content style=page-break-before:always><h1 id=pg-f4c0cdc5efc0b99d834a7ed2753ed1eb>7 - 集群管理</h1></div><div class=td-content><h1 id=pg-d4134e0b428e3d8466977e543f95303d>7.1 - 以独立模式运行 kubelet</h1><p>本教程将向你展示如何运行一个独立的 kubelet 实例。</p><p>你可能会有不同的动机来运行一个独立的 kubelet。 本教程旨在向你介绍 Kubernetes,即使你对此并没有太多经验也没有关系。 你可以跟随本教程学习节点设置、基本(静态)Pod 以及 Kubernetes 如何管理容器。</p><p>你学习完本教程后,就可以尝试使用带一个<a class=glossary-tooltip title='控制平面是指容器编排层,它暴露 API 和接口来定义、部署容器和管理容器的生命周期。' data-toggle=tooltip data-placement=top href='/zh-cn/docs/reference/glossary/?all=true#term-control-plane' target=_blank aria-label=控制平面>控制平面</a>的集群来管理 Pod、节点和其他类别的对象。例如,<a href=/zh-cn/docs/tutorials/hello-minikube/>你好,Minikube</a>。</p><p>你还可以以独立模式运行 kubelet 来满足生产场景要求,例如为高可用、弹性部署的集群运行控制平面。 本教程不涵盖运行弹性控制平面所需的细节。</p><h2 id=教程目标>教程目标</h2><ul><li>在 Linux 系统上安装 <code>cri-o</code> 和 <code>kubelet</code>,并将其作为 <code>systemd</code> 服务运行。</li><li>启动一个运行 <code>nginx</code> 的 Pod,监听针对此 Pod 的 IP 地址的 TCP 80 端口的请求。</li><li>学习此方案中不同组件之间如何交互。</li></ul><div class="alert alert-caution" role=alert><h4 class=alert-heading>注意:</h4><p>本教程中所使用的 kubelet 配置在设计上是不安全的,<strong>不</strong>得用于生产环境中。</p></div><h2 id=准备开始>准备开始</h2><ul><li>对使用 <code>systemd</code> 和 <code>iptables</code>(或使用 <code>iptables</code> 仿真的 nftables)的 Linux 系统具有管理员(<code>root</code>)访问权限。</li><li>有权限访问互联网以下载本教程所需的组件,例如:<ul><li>实现 Kubernetes <a class=glossary-tooltip title='一组与 kubelet 集成的容器运行时 API' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/architecture/#container-runtime target=_blank aria-label=CRI>CRI</a> 的<a class=glossary-tooltip title=容器运行时是负责运行容器的软件。 data-toggle=tooltip data-placement=top href=/zh-cn/docs/setup/production-environment/container-runtimes target=_blank aria-label=容器运行时>容器运行时</a>。</li><li>网络插件(通常称为 <a class=glossary-tooltip title='容器网络接口 (Container network interface;CNI) 插件是遵循 appc/CNI 协议的一类网络插件。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/ target=_blank aria-label=容器网络接口(CNI)>容器网络接口(CNI)</a>)。</li><li>必需的 CLI 工具:<code>curl</code>、<code>tar</code>、<code>jq</code>。</li></ul></li></ul><h2 id=prepare-the-system>准备好系统</h2><h3 id=swap-configuration>配置内存交换</h3><p>默认情况下,如果在节点上检测到内存交换,kubelet 将启动失败。 这意味着内存交换应该被禁用或被 kubelet 容忍。</p><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>如果你配置 kubelet 为容忍内存交换,则 kubelet 仍会配置 Pod(以及这些 Pod 中的容器)不使用交换空间。 要了解 Pod 实际上可以如何使用可用的交换,你可以进一步阅读 Linux 节点上<a href=/zh-cn/docs/concepts/architecture/nodes/#swap-memory>交换内存管理</a>。</p></div><p>如果你启用了交换内存,则禁用它或在 kubelet 配置文件中添加 <code>failSwapOn: false</code>。</p><p>要检查交换内存是否被启用:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>sudo swapon --show </span></span></code></pre></div><p>如果此命令没有输出,则交换内存已被禁用。</p><p>临时禁用交换内存:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>sudo swapoff -a </span></span></code></pre></div><p>要使此变更持续到重启之后:</p><p>确保在 <code>/etc/fstab</code> 或 <code>systemd.swap</code> 中禁用交换内存,具体取决于它在你的系统上是如何配置的。</p><h3 id=enable-ipv4-packet-forwarding>启用 IPv4 数据包转发</h3><p>检查 IPv4 数据包转发是否被启用:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>cat /proc/sys/net/ipv4/ip_forward </span></span></code></pre></div><p>如果输出为 <code>1</code>,则 IPv4 数据包转发已被启用。如果输出为 <code>0</code>,按照以下步骤操作。</p><p>要启用 IPv4 数据包转发,创建一个配置文件,将 <code>net.ipv4.ip_forward</code> 参数设置为 <code>1</code>:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>sudo tee /etc/sysctl.d/k8s.conf <span style=color:#b44><<EOF </span></span></span><span style=display:flex><span><span style=color:#b44>net.ipv4.ip_forward = 1 </span></span></span><span style=display:flex><span><span style=color:#b44>EOF</span> </span></span></code></pre></div><p>将变更应用到系统:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>sudo sysctl --system </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>... * Applying /etc/sysctl.d/k8s.conf ... net.ipv4.ip_forward = 1 * Applying /etc/sysctl.conf ... </code></pre><h2 id=download-install-and-configure-the-components>下载、安装和配置组件</h2><div class="alert alert-secondary callout third-party-content" role=alert><strong>说明:</strong> 本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循<a href=https://github.com/cncf/foundation/blob/master/website-guidelines.md target=_blank>CNCF 网站指南</a>,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读<a href=/zh-cn/docs/contribute/style/content-guide/#third-party-content>内容指南</a>。</div><h3 id=container-runtime>安装容器运行时</h3><p>下载所需软件包的最新可用版本(推荐)。</p><p>本教程建议安装 <a href=https://github.com/cri-o/cri-o>CRI-O 容器运行时</a>(外部链接)。</p><p>根据你安装的特定 Linux 发行版,有几种<a href=https://github.com/cri-o/cri-o/blob/main/install.md>安装容器运行时的方式</a>。 尽管 CRI-O 推荐使用 <code>deb</code> 或 <code>rpm</code> 包,但本教程使用 <a href=https://github.com/cri-o/packaging/blob/main/README.md>CRI-O Packaging 项目</a>中的<strong>静态二进制包</strong>脚本, 以简化整个安装过程,并保持与 Linux 发行版无关。</p><p>此脚本安装并配置更多必需的软件,例如容器联网所用的 <a href=https://github.com/containernetworking/cni><code>cni-plugins</code></a> 以及运行容器所用的 <a href=https://github.com/containers/crun><code>crun</code></a> 和 <a href=https://github.com/opencontainers/runc><code>runc</code></a>。</p><p>此脚本将自动检测系统的处理器架构(<code>amd64</code> 或 <code>arm64</code>),并选择和安装最新版本的软件包。</p><h3 id=cri-o-setup>设置 CRI-O</h3><p>查阅<a href=https://github.com/cri-o/cri-o/releases>发布版本</a>页面(外部链接)。</p><p>下载静态二进制包脚本:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>curl https://raw.githubusercontent.com/cri-o/packaging/main/get > crio-install </span></span></code></pre></div><p>运行安装器脚本:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>sudo bash crio-install </span></span></code></pre></div><p>启用并启动 <code>crio</code> 服务:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>sudo systemctl daemon-reload </span></span><span style=display:flex><span>sudo systemctl <span style=color:#a2f>enable</span> --now crio.service </span></span></code></pre></div><p>快速测试:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>sudo systemctl is-active crio.service </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>active </code></pre><p>详细的服务检查:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>sudo journalctl -f -u crio.service </span></span></code></pre></div><h3 id=install-network-plugins>安装网络插件</h3><p><code>cri-o</code> 安装器安装并配置 <code>cni-plugins</code> 包。你可以通过运行以下命令来验证安装包:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>/opt/cni/bin/bridge --version </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>CNI bridge plugin v1.5.1 CNI protocol versions supported: 0.1.0, 0.2.0, 0.3.0, 0.3.1, 0.4.0, 1.0.0 </code></pre><p>检查默认配置:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>cat /etc/cni/net.d/11-crio-ipv4-bridge.conflist </span></span></code></pre></div><p>输出类似于:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-json data-lang=json><span style=display:flex><span>{ </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"cniVersion"</span>: <span style=color:#b44>"1.0.0"</span>, </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"name"</span>: <span style=color:#b44>"crio"</span>, </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"plugins"</span>: [ </span></span><span style=display:flex><span> { </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"type"</span>: <span style=color:#b44>"bridge"</span>, </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"bridge"</span>: <span style=color:#b44>"cni0"</span>, </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"isGateway"</span>: <span style=color:#a2f;font-weight:700>true</span>, </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"ipMasq"</span>: <span style=color:#a2f;font-weight:700>true</span>, </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"hairpinMode"</span>: <span style=color:#a2f;font-weight:700>true</span>, </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"ipam"</span>: { </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"type"</span>: <span style=color:#b44>"host-local"</span>, </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"routes"</span>: [ </span></span><span style=display:flex><span> { <span style=color:green;font-weight:700>"dst"</span>: <span style=color:#b44>"0.0.0.0/0"</span> } </span></span><span style=display:flex><span> ], </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"ranges"</span>: [ </span></span><span style=display:flex><span> [{ <span style=color:green;font-weight:700>"subnet"</span>: <span style=color:#b44>"10.85.0.0/16"</span> }] </span></span><span style=display:flex><span> ] </span></span><span style=display:flex><span> } </span></span><span style=display:flex><span> } </span></span><span style=display:flex><span> ] </span></span><span style=display:flex><span>} </span></span></code></pre></div><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>确保默认的 <code>subnet</code> 范围(<code>10.85.0.0/16</code>)不会与你已经在使用的任一网络地址重叠。 如果出现重叠,你可以编辑此文件并进行相应的更改。更改后重启服务。</p></div><h3 id=download-and-set-up-the-kubelet>下载并设置 kubelet</h3><p>下载 kubelet 的<a href=/zh-cn/releases/download/>最新稳定版本</a>。</p><ul class="nav nav-tabs" id=download-kubelet role=tablist><li class=nav-item><a data-toggle=tab class="nav-link active" href=#download-kubelet-0 role=tab aria-controls=download-kubelet-0 aria-selected=true>x86-64</a></li><li class=nav-item><a data-toggle=tab class=nav-link href=#download-kubelet-1 role=tab aria-controls=download-kubelet-1>ARM64</a></li></ul><div class=tab-content id=download-kubelet><div id=download-kubelet-0 class="tab-pane show active" role=tabpanel aria-labelledby=download-kubelet-0><p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-bash data-lang=bash><span style=display:flex><span> </span></span><span style=display:flex><span>curl -LO <span style=color:#b44>"https://dl.k8s.io/release/</span><span style=color:#a2f;font-weight:700>$(</span>curl -L -s https://dl.k8s.io/release/stable.txt<span style=color:#a2f;font-weight:700>)</span><span style=color:#b44>/bin/linux/amd64/kubelet"</span> </span></span></code></pre></div></div><div id=download-kubelet-1 class=tab-pane role=tabpanel aria-labelledby=download-kubelet-1><p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-bash data-lang=bash><span style=display:flex><span> </span></span><span style=display:flex><span>curl -LO <span style=color:#b44>"https://dl.k8s.io/release/</span><span style=color:#a2f;font-weight:700>$(</span>curl -L -s https://dl.k8s.io/release/stable.txt<span style=color:#a2f;font-weight:700>)</span><span style=color:#b44>/bin/linux/arm64/kubelet"</span> </span></span></code></pre></div></div></div><p>配置:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>sudo mkdir -p /etc/kubernetes/manifests </span></span></code></pre></div><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>sudo tee /etc/kubernetes/kubelet.yaml <span style=color:#b44><<EOF </span></span></span><span style=display:flex><span><span style=color:#b44>apiVersion: kubelet.config.k8s.io/v1beta1 </span></span></span><span style=display:flex><span><span style=color:#b44>kind: KubeletConfiguration </span></span></span><span style=display:flex><span><span style=color:#b44>authentication: </span></span></span><span style=display:flex><span><span style=color:#b44> webhook: </span></span></span><span style=display:flex><span><span style=color:#b44> enabled: false # 请勿在生产集群中使用! </span></span></span><span style=display:flex><span><span style=color:#b44>authorization: </span></span></span><span style=display:flex><span><span style=color:#b44> mode: AlwaysAllow # 请勿在生产集群中使用! </span></span></span><span style=display:flex><span><span style=color:#b44>enableServer: false </span></span></span><span style=display:flex><span><span style=color:#b44>logging: </span></span></span><span style=display:flex><span><span style=color:#b44> format: text </span></span></span><span style=display:flex><span><span style=color:#b44>address: 127.0.0.1 # 限制对 localhost 的访问 </span></span></span><span style=display:flex><span><span style=color:#b44>readOnlyPort: 10255 # 请勿在生产集群中使用! </span></span></span><span style=display:flex><span><span style=color:#b44>staticPodPath: /etc/kubernetes/manifests </span></span></span><span style=display:flex><span><span style=color:#b44>containerRuntimeEndpoint: unix:///var/run/crio/crio.sock </span></span></span><span style=display:flex><span><span style=color:#b44>EOF</span> </span></span></code></pre></div><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>由于你搭建的不是一个生产集群,所以你可以使用明文 HTTP(<code>readOnlyPort: 10255</code>)对 kubelet API 进行不做身份认证的查询。</p><p>为了顺利完成本次教学,<strong>身份认证 Webhook</strong> 被禁用,<strong>鉴权模式</strong>被设置为 <code>AlwaysAllow</code>。 你可以进一步了解<a href=/zh-cn/docs/reference/access-authn-authz/authorization/#authorization-modules>鉴权模式</a>和 <a href=/zh-cn/docs/reference/access-authn-authz/webhook/>Webhook 身份认证</a>, 以正确地配置 kubelet 在你的环境中以独立模式运行。</p><p>参阅<a href=/zh-cn/docs/reference/networking/ports-and-protocols/>端口和协议</a>以了解 Kubernetes 组件使用的端口。</p></div><p>安装:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>chmod +x kubelet </span></span><span style=display:flex><span>sudo cp kubelet /usr/bin/ </span></span></code></pre></div><p>创建 <code>systemd</code> 服务单元文件:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>sudo tee /etc/systemd/system/kubelet.service <span style=color:#b44><<EOF </span></span></span><span style=display:flex><span><span style=color:#b44>[Unit] </span></span></span><span style=display:flex><span><span style=color:#b44>Description=Kubelet </span></span></span><span style=display:flex><span><span style=color:#b44> </span></span></span><span style=display:flex><span><span style=color:#b44>[Service] </span></span></span><span style=display:flex><span><span style=color:#b44>ExecStart=/usr/bin/kubelet \ </span></span></span><span style=display:flex><span><span style=color:#b44> --config=/etc/kubernetes/kubelet.yaml </span></span></span><span style=display:flex><span><span style=color:#b44>Restart=always </span></span></span><span style=display:flex><span><span style=color:#b44> </span></span></span><span style=display:flex><span><span style=color:#b44>[Install] </span></span></span><span style=display:flex><span><span style=color:#b44>WantedBy=multi-user.target </span></span></span><span style=display:flex><span><span style=color:#b44>EOF</span> </span></span></code></pre></div><p>服务配置文件中故意省略了命令行参数 <code>--kubeconfig</code>。此参数设置 <a href=/zh-cn/docs/concepts/configuration/organize-cluster-access-kubeconfig/>kubeconfig</a> 文件的路径,指定如何连接到 API 服务器,以启用 API 服务器模式。省略此参数将启用独立模式。</p><p>启用并启动 <code>kubelet</code> 服务:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>sudo systemctl daemon-reload </span></span><span style=display:flex><span>sudo systemctl <span style=color:#a2f>enable</span> --now kubelet.service </span></span></code></pre></div><p>快速测试:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>sudo systemctl is-active kubelet.service </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>active </code></pre><p>详细的服务检查:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>sudo journalctl -u kubelet.service </span></span></code></pre></div><p>检查 kubelet 的 API <code>/healthz</code> 端点:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>curl http://localhost:10255/healthz?verbose </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>[+]ping ok [+]log ok [+]syncloop ok healthz check passed </code></pre><p>查询 kubelet 的 API <code>/pods</code> 端点:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>curl http://localhost:10255/pods | jq <span style=color:#b44>'.'</span> </span></span></code></pre></div><p>输出类似于:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-json data-lang=json><span style=display:flex><span>{ </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"kind"</span>: <span style=color:#b44>"PodList"</span>, </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"apiVersion"</span>: <span style=color:#b44>"v1"</span>, </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"metadata"</span>: {}, </span></span><span style=display:flex><span> <span style=color:green;font-weight:700>"items"</span>: <span style=color:#a2f;font-weight:700>null</span> </span></span><span style=display:flex><span>} </span></span></code></pre></div><h2 id=run-a-pod-in-the-kubelet>在 kubelet 中运行 Pod</h2><p>在独立模式下,你可以使用 Pod 清单运行 Pod。这些清单可以放在本地文件系统上,或通过 HTTP 从配置源获取。</p><p>为 Pod 创建一个清单:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>cat <span style=color:#b44><<EOF > static-web.yaml </span></span></span><span style=display:flex><span><span style=color:#b44>apiVersion: v1 </span></span></span><span style=display:flex><span><span style=color:#b44>kind: Pod </span></span></span><span style=display:flex><span><span style=color:#b44>metadata: </span></span></span><span style=display:flex><span><span style=color:#b44> name: static-web </span></span></span><span style=display:flex><span><span style=color:#b44>spec: </span></span></span><span style=display:flex><span><span style=color:#b44> containers: </span></span></span><span style=display:flex><span><span style=color:#b44> - name: web </span></span></span><span style=display:flex><span><span style=color:#b44> image: nginx </span></span></span><span style=display:flex><span><span style=color:#b44> ports: </span></span></span><span style=display:flex><span><span style=color:#b44> - name: web </span></span></span><span style=display:flex><span><span style=color:#b44> containerPort: 80 </span></span></span><span style=display:flex><span><span style=color:#b44> protocol: TCP </span></span></span><span style=display:flex><span><span style=color:#b44>EOF</span> </span></span></code></pre></div><p>将 <code>static-web.yaml</code> 清单文件复制到 <code>/etc/kubernetes/manifests</code> 目录。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>sudo cp static-web.yaml /etc/kubernetes/manifests/ </span></span></code></pre></div><h3 id=find-out-information>查找 kubelet 和 Pod 的信息</h3><p>Pod 网络插件为每个 Pod 创建一个网络桥(<code>cni0</code>)和一对 <code>veth</code> 接口 (这对接口的其中一个接口在新创建的 Pod 内,另一个接口在主机层面)。</p><p>查询 kubelet 的 API 端点 <code>http://localhost:10255/pods</code>:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>curl http://localhost:10255/pods | jq <span style=color:#b44>'.'</span> </span></span></code></pre></div><p>要获取 <code>static-web</code> Pod 的 IP 地址:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>curl http://localhost:10255/pods | jq <span style=color:#b44>'.items[].status.podIP'</span> </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>"10.85.0.4" </code></pre><p>连接到 <code>nginx</code> 服务器 Pod,地址为 <code>http://<IP>:<Port></code>(端口 80 是默认端口),在本例中为:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>curl http://10.85.0.4 </span></span></code></pre></div><p>输出类似于:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-html data-lang=html><span style=display:flex><span><span style=color:#080><!DOCTYPE html></span> </span></span><span style=display:flex><span><<span style=color:green;font-weight:700>html</span>> </span></span><span style=display:flex><span><<span style=color:green;font-weight:700>head</span>> </span></span><span style=display:flex><span><<span style=color:green;font-weight:700>title</span>>Welcome to nginx!</<span style=color:green;font-weight:700>title</span>> </span></span><span style=display:flex><span>... </span></span></code></pre></div><h2 id=where-to-look-for-more-details>了解更多细节</h2><p>如果你需要排查在学习本教程时遇到的问题,你可以在以下目录中查找监控和故障排查资料:</p><pre tabindex=0><code>/var/lib/cni /var/lib/containers /var/lib/kubelet /var/log/containers /var/log/pods </code></pre><h2 id=clean-up>清理</h2><h3 id=kubelet>kubelet</h3><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>sudo systemctl disable --now kubelet.service </span></span><span style=display:flex><span>sudo systemctl daemon-reload </span></span><span style=display:flex><span>sudo rm /etc/systemd/system/kubelet.service </span></span><span style=display:flex><span>sudo rm /usr/bin/kubelet </span></span><span style=display:flex><span>sudo rm -rf /etc/kubernetes </span></span><span style=display:flex><span>sudo rm -rf /var/lib/kubelet </span></span><span style=display:flex><span>sudo rm -rf /var/log/containers </span></span><span style=display:flex><span>sudo rm -rf /var/log/pods </span></span></code></pre></div><h3 id=container-runtime>容器运行时</h3><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>sudo systemctl disable --now crio.service </span></span><span style=display:flex><span>sudo systemctl daemon-reload </span></span><span style=display:flex><span>sudo rm -rf /usr/local/bin </span></span><span style=display:flex><span>sudo rm -rf /usr/local/lib </span></span><span style=display:flex><span>sudo rm -rf /usr/local/share </span></span><span style=display:flex><span>sudo rm -rf /usr/libexec/crio </span></span><span style=display:flex><span>sudo rm -rf /etc/crio </span></span><span style=display:flex><span>sudo rm -rf /etc/containers </span></span></code></pre></div><h3 id=network-plugins>网络插件</h3><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>sudo rm -rf /opt/cni </span></span><span style=display:flex><span>sudo rm -rf /etc/cni </span></span><span style=display:flex><span>sudo rm -rf /var/lib/cni </span></span></code></pre></div><h2 id=conclusion>结论</h2><p>本页涵盖了以独立模式部署 kubelet 的各个基本方面。你现在可以部署 Pod 并测试更多功能。</p><p>请注意,在独立模式下,kubelet <strong>不</strong>支持从控制平面获取 Pod 配置(因为没有控制平面连接)。</p><p>你还不能使用 <a class=glossary-tooltip title='ConfigMap 是一种 API 对象,用来将非机密性的数据保存到键值对中。使用时可以用作环境变量、命令行参数或者存储卷中的配置文件。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/tasks/configure-pod-container/configure-pod-configmap/ target=_blank aria-label=ConfigMap>ConfigMap</a> 或 <a class=glossary-tooltip title='Secret 用于存储敏感信息,如密码、 OAuth 令牌和 SSH 密钥。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/configuration/secret/ target=_blank aria-label=Secret>Secret</a> 来配置静态 Pod 中的容器。</p><h2 id=接下来>接下来</h2><ul><li>跟随<a href=/zh-cn/docs/tutorials/hello-minikube/>你好,Minikube</a> 学习如何在<strong>有</strong>控制平面的情况下运行 Kubernetes。minikube 工具帮助你在自己的计算机上搭建一个练习集群。</li><li>进一步了解<a href=/zh-cn/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/>网络插件</a></li><li>进一步了解<a href=/zh-cn/docs/setup/production-environment/container-runtimes/>容器运行时</a></li><li>进一步了解 <a href=/zh-cn/docs/reference/command-line-tools-reference/kubelet/>kubelet</a></li><li>进一步了解<a href=/zh-cn/docs/tasks/configure-pod-container/static-pod/>静态 Pod</a></li></ul></div><div class=td-content style=page-break-before:always><h1 id=pg-97489f0aa8ac2df31a0d6b444a7bde62>8 - Service</h1></div><div class=td-content><h1 id=pg-bc0a2760d2865e91c501bc2467cd1a4b>8.1 - 使用 Service 连接到应用</h1><h2 id=the-kubernetes-model-for-connecting-containers>Kubernetes 连接容器的模型</h2><p>既然有了一个持续运行、可复制的应用,我们就能够将它暴露到网络上。</p><p>Kubernetes 假设 Pod 可与其它 Pod 通信,不管它们在哪个主机上。 Kubernetes 给每一个 Pod 分配一个集群私有 IP 地址,所以没必要在 Pod 与 Pod 之间创建连接或将容器的端口映射到主机端口。 这意味着同一个 Pod 内的所有容器能通过 localhost 上的端口互相连通,集群中的所有 Pod 也不需要通过 NAT 转换就能够互相看到。 本文档的剩余部分详述如何在上述网络模型之上运行可靠的服务。</p><p>本教程使用一个简单的 Nginx 服务器来演示概念验证原型。</p><h2 id=exposing-pods-to-the-cluster>在集群中暴露 Pod</h2><p>我们在之前的示例中已经做过,然而让我们以网络连接的视角再重做一遍。 创建一个 Nginx Pod,注意其中包含一个容器端口的规约:</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/service/networking/run-my-nginx.yaml download=service/networking/run-my-nginx.yaml><code>service/networking/run-my-nginx.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("service-networking-run-my-nginx-yaml")' title="复制 service/networking/run-my-nginx.yaml 到剪贴板"></img></div><div class=includecode id=service-networking-run-my-nginx-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>apps/v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Deployment<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>my-nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>matchLabels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>run</span>:<span style=color:#bbb> </span>my-nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>replicas</span>:<span style=color:#bbb> </span><span style=color:#666>2</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>template</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>run</span>:<span style=color:#bbb> </span>my-nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>my-nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>containerPort</span>:<span style=color:#bbb> </span><span style=color:#666>80</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span></span></span></code></pre></div></div></div><p>这使得可以从集群中任何一个节点来访问它。检查节点,该 Pod 正在运行:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f ./run-my-nginx.yaml </span></span><span style=display:flex><span>kubectl get pods -l <span style=color:#b8860b>run</span><span style=color:#666>=</span>my-nginx -o wide </span></span></code></pre></div><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE IP NODE my-nginx-3800858182-jr4a2 1/1 Running 0 13s 10.244.3.4 kubernetes-minion-905m my-nginx-3800858182-kna2y 1/1 Running 0 13s 10.244.2.5 kubernetes-minion-ljyd </code></pre><p>检查 Pod 的 IP 地址:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods -l <span style=color:#b8860b>run</span><span style=color:#666>=</span>my-nginx -o custom-columns<span style=color:#666>=</span>POD_IP:.status.podIPs </span></span><span style=display:flex><span> POD_IP </span></span><span style=display:flex><span> <span style=color:#666>[</span>map<span style=color:#666>[</span>ip:10.244.3.4<span style=color:#666>]]</span> </span></span><span style=display:flex><span> <span style=color:#666>[</span>map<span style=color:#666>[</span>ip:10.244.2.5<span style=color:#666>]]</span> </span></span></code></pre></div><p>你应该能够通过 ssh 登录到集群中的任何一个节点上,并使用诸如 <code>curl</code> 之类的工具向这两个 IP 地址发出查询请求。 需要注意的是,容器 <strong>不会</strong> 使用该节点上的 80 端口,也不会使用任何特定的 NAT 规则去路由流量到 Pod 上。 这意味着你可以使用相同的 <code>containerPort</code> 在同一个节点上运行多个 Nginx Pod, 并且可以从集群中任何其他的 Pod 或节点上使用为 Pod 分配的 IP 地址访问到它们。 如果你想的话,你依然可以将宿主节点的某个端口的流量转发到 Pod 中,但是出于网络模型的原因,你不必这么做。</p><p>如果对此好奇,请参考 <a href=/zh-cn/docs/concepts/cluster-administration/networking/#the-kubernetes-network-model>Kubernetes 网络模型</a>。</p><h2 id=creating-a-service>创建 Service</h2><p>我们有一组在一个扁平的、集群范围的地址空间中运行 Nginx 服务的 Pod。 理论上,你可以直接连接到这些 Pod,但如果某个节点宕机会发生什么呢? Pod 会终止,Deployment 内的 ReplicaSet 将创建新的 Pod,且使用不同的 IP。这正是 Service 要解决的问题。</p><p>Kubernetes Service 是集群中提供相同功能的一组 Pod 的抽象表达。 当每个 Service 创建时,会被分配一个唯一的 IP 地址(也称为 clusterIP)。 这个 IP 地址与 Service 的生命周期绑定在一起,只要 Service 存在,它就不会改变。 可以配置 Pod 使它与 Service 进行通信,Pod 知道与 Service 通信将被自动地负载均衡到该 Service 中的某些 Pod 上。</p><p>可以使用 <code>kubectl expose</code> 命令为 2 个 Nginx 副本创建一个 Service:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl expose deployment/my-nginx </span></span></code></pre></div><pre tabindex=0><code>service/my-nginx exposed </code></pre><p>这等价于使用 <code>kubectl create -f</code> 命令及如下的 yaml 文件创建:</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/service/networking/nginx-svc.yaml download=service/networking/nginx-svc.yaml><code>service/networking/nginx-svc.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("service-networking-nginx-svc-yaml")' title="复制 service/networking/nginx-svc.yaml 到剪贴板"></img></div><div class=includecode id=service-networking-nginx-svc-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Service<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>my-nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>run</span>:<span style=color:#bbb> </span>my-nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>port</span>:<span style=color:#bbb> </span><span style=color:#666>80</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>protocol</span>:<span style=color:#bbb> </span>TCP<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>run</span>:<span style=color:#bbb> </span>my-nginx<span style=color:#bbb> </span></span></span></code></pre></div></div></div><p>上述规约将创建一个 Service,该 Service 会将所有具有标签 <code>run: my-nginx</code> 的 Pod 的 TCP 80 端口暴露到一个抽象的 Service 端口上(<code>targetPort</code>:容器接收流量的端口;<code>port</code>: 可任意取值的抽象的 Service 端口,其他 Pod 通过该端口访问 Service)。 查看 <a href=/docs/reference/generated/kubernetes-api/v1.31/#service-v1-core>Service</a> API 对象以了解 Service 所能接受的字段列表。 查看你的 Service 资源:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get svc my-nginx </span></span></code></pre></div><pre tabindex=0><code>NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE my-nginx ClusterIP 10.0.162.149 <none> 80/TCP 21s </code></pre><p>正如前面所提到的,一个 Service 由一组 Pod 提供支撑。这些 Pod 通过 <a class=glossary-tooltip title='一种将网络端点与 Kubernetes 资源组合在一起的方法。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/services-networking/endpoint-slices/ target=_blank aria-label=EndpointSlices>EndpointSlices</a> 暴露出来。 Service Selector 将持续评估,结果被 POST 到使用<a class=glossary-tooltip title=用来为对象设置可标识的属性标记;这些标记对用户而言是有意义且重要的。 data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/overview/working-with-objects/labels/ target=_blank aria-label=标签>标签</a>与该 Service 连接的一个 EndpointSlice。 当 Pod 终止后,它会自动从包含该 Pod 的 EndpointSlices 中移除。 新的能够匹配上 Service Selector 的 Pod 将被自动地为该 Service 添加到 EndpointSlice 中。 检查 Endpoint,注意到 IP 地址与在第一步创建的 Pod 是相同的。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl describe svc my-nginx </span></span></code></pre></div><pre tabindex=0><code>Name: my-nginx Namespace: default Labels: run=my-nginx Annotations: <none> Selector: run=my-nginx Type: ClusterIP IP Family Policy: SingleStack IP Families: IPv4 IP: 10.0.162.149 IPs: 10.0.162.149 Port: <unset> 80/TCP TargetPort: 80/TCP Endpoints: 10.244.2.5:80,10.244.3.4:80 Session Affinity: None Events: <none> </code></pre><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get endpointslices -l kubernetes.io/service-name<span style=color:#666>=</span>my-nginx </span></span></code></pre></div><pre tabindex=0><code>NAME ADDRESSTYPE PORTS ENDPOINTS AGE my-nginx-7vzhx IPv4 80 10.244.2.5,10.244.3.4 21s </code></pre><p>现在,你应该能够从集群中任意节点上使用 curl 命令向 <code><CLUSTER-IP>:<PORT></code> 发送请求以访问 Nginx Service。 注意 Service IP 完全是虚拟的,它从来没有走过网络,如果对它如何工作的原理感到好奇, 可以进一步阅读<a href=/zh-cn/docs/reference/networking/virtual-ips/>服务代理</a>的内容。</p><h2 id=accessing-the-service>访问 Service</h2><p>Kubernetes 支持两种查找服务的主要模式:环境变量和 DNS。前者开箱即用,而后者则需要 <a href=https://releases.k8s.io/v1.31.0/cluster/addons/dns/coredns>CoreDNS 集群插件</a>。</p><div class="alert alert-info" role=alert><h4 class=alert-heading>说明:</h4><p>如果不需要服务环境变量(因为可能与预期的程序冲突,可能要处理的变量太多,或者仅使用DNS等),则可以通过在 <a href=/docs/reference/generated/kubernetes-api/v1.31/#pod-v1-core>pod spec</a> 上将 <code>enableServiceLinks</code> 标志设置为 <code>false</code> 来禁用此模式。</p></div><h3 id=environment-variables>环境变量</h3><p>当 Pod 在节点上运行时,kubelet 会针对每个活跃的 Service 为 Pod 添加一组环境变量。 这就引入了一个顺序的问题。为解释这个问题,让我们先检查正在运行的 Nginx Pod 的环境变量(你的环境中的 Pod 名称将会与下面示例命令中的不同):</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl <span style=color:#a2f>exec</span> my-nginx-3800858182-jr4a2 -- printenv | grep SERVICE </span></span></code></pre></div><pre tabindex=0><code>KUBERNETES_SERVICE_HOST=10.0.0.1 KUBERNETES_SERVICE_PORT=443 KUBERNETES_SERVICE_PORT_HTTPS=443 </code></pre><p>能看到环境变量中并没有你创建的 Service 相关的值。这是因为副本的创建先于 Service。 这样做的另一个缺点是,调度器可能会将所有 Pod 部署到同一台机器上,如果该机器宕机则整个 Service 都会离线。 要改正的话,我们可以先终止这 2 个 Pod,然后等待 Deployment 去重新创建它们。 这次 Service 会 <strong>先于</strong> 副本存在。这将实现调度器级别的 Pod 按 Service 分布(假定所有的节点都具有同样的容量),并提供正确的环境变量:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl scale deployment my-nginx --replicas<span style=color:#666>=</span>0; kubectl scale deployment my-nginx --replicas<span style=color:#666>=</span>2; </span></span><span style=display:flex><span>kubectl get pods -l <span style=color:#b8860b>run</span><span style=color:#666>=</span>my-nginx -o wide </span></span></code></pre></div><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE IP NODE my-nginx-3800858182-e9ihh 1/1 Running 0 5s 10.244.2.7 kubernetes-minion-ljyd my-nginx-3800858182-j4rm4 1/1 Running 0 5s 10.244.3.8 kubernetes-minion-905m </code></pre><p>你可能注意到,Pod 具有不同的名称,这是因为它们是被重新创建的。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl <span style=color:#a2f>exec</span> my-nginx-3800858182-e9ihh -- printenv | grep SERVICE </span></span></code></pre></div><pre tabindex=0><code>KUBERNETES_SERVICE_PORT=443 MY_NGINX_SERVICE_HOST=10.0.162.149 KUBERNETES_SERVICE_HOST=10.0.0.1 MY_NGINX_SERVICE_PORT=80 KUBERNETES_SERVICE_PORT_HTTPS=443 </code></pre><h3 id=dns>DNS</h3><p>Kubernetes 提供了一个自动为其它 Service 分配 DNS 名字的 DNS 插件 Service。 你可以通过如下命令检查它是否在工作:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get services kube-dns --namespace<span style=color:#666>=</span>kube-system </span></span></code></pre></div><pre tabindex=0><code>NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kube-dns ClusterIP 10.0.0.10 <none> 53/UDP,53/TCP 8m </code></pre><p>本段剩余的内容假设你已经有一个拥有持久 IP 地址的 Service(my-nginx),以及一个为其 IP 分配名称的 DNS 服务器。 这里我们使用 CoreDNS 集群插件(应用名为 <code>kube-dns</code>), 所以在集群中的任何 Pod 中,你都可以使用标准方法(例如:<code>gethostbyname()</code>)与该 Service 通信。 如果 CoreDNS 没有在运行,你可以参照 <a href=https://github.com/coredns/deployment/tree/master/kubernetes>CoreDNS README</a> 或者<a href=/zh-cn/docs/tasks/administer-cluster/coredns/#installing-coredns>安装 CoreDNS</a> 来启用它。 让我们运行另一个 curl 应用来进行测试:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl run curl --image<span style=color:#666>=</span>radial/busyboxplus:curl -i --tty --rm </span></span></code></pre></div><pre tabindex=0><code>Waiting for pod default/curl-131556218-9fnch to be running, status is Pending, pod ready: false Hit enter for command prompt </code></pre><p>然后,按回车并执行命令 <code>nslookup my-nginx</code>:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#666>[</span> root@curl-131556218-9fnch:/ <span style=color:#666>]</span>$ nslookup my-nginx </span></span><span style=display:flex><span>Server: 10.0.0.10 </span></span><span style=display:flex><span>Address 1: 10.0.0.10 </span></span><span style=display:flex><span> </span></span><span style=display:flex><span>Name: my-nginx </span></span><span style=display:flex><span>Address 1: 10.0.162.149 </span></span></code></pre></div><h2 id=securing-the-service>保护 Service</h2><p>到现在为止,我们只在集群内部访问了 Nginx 服务器。在将 Service 暴露到因特网之前,我们希望确保通信信道是安全的。 为实现这一目的,需要:</p><ul><li>用于 HTTPS 的自签名证书(除非已经有了一个身份证书)</li><li>使用证书配置的 Nginx 服务器</li><li>使 Pod 可以访问证书的 <a href=/zh-cn/docs/concepts/configuration/secret/>Secret</a></li></ul><p>你可以从 <a href=https://github.com/kubernetes/examples/tree/master/staging/https-nginx/>Nginx https 示例</a>获取所有上述内容。 你需要安装 go 和 make 工具。如果你不想安装这些软件,可以按照后文所述的手动执行步骤执行操作。简要过程如下:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>make keys <span style=color:#b8860b>KEY</span><span style=color:#666>=</span>/tmp/nginx.key <span style=color:#b8860b>CERT</span><span style=color:#666>=</span>/tmp/nginx.crt </span></span><span style=display:flex><span>kubectl create secret tls nginxsecret --key /tmp/nginx.key --cert /tmp/nginx.crt </span></span></code></pre></div><pre tabindex=0><code>secret/nginxsecret created </code></pre><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get secrets </span></span></code></pre></div><pre tabindex=0><code>NAME TYPE DATA AGE nginxsecret kubernetes.io/tls 2 1m </code></pre><p>以下是 configmap:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl create configmap nginxconfigmap --from-file<span style=color:#666>=</span>default.conf </span></span></code></pre></div><p>你可以在 <a href=https://github.com/kubernetes/examples/tree/bc9ca4ca32bb28762ef216386934bef20f1f9930/staging/https-nginx/>Kubernetes examples 项目代码仓库</a>中找到 <code>default.conf</code> 示例。</p><pre tabindex=0><code>configmap/nginxconfigmap created </code></pre><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get configmaps </span></span></code></pre></div><pre tabindex=0><code>NAME DATA AGE nginxconfigmap 1 114s </code></pre><p>你可以使用以下命令来查看 <code>nginxconfigmap</code> ConfigMap 的细节:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl describe configmap nginxconfigmap </span></span></code></pre></div><p>输出类似于:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-console data-lang=console><span style=display:flex><span><span style=color:#888>Name: nginxconfigmap </span></span></span><span style=display:flex><span><span style=color:#888>Namespace: default </span></span></span><span style=display:flex><span><span style=color:#888>Labels: <none> </span></span></span><span style=display:flex><span><span style=color:#888>Annotations: <none> </span></span></span><span style=display:flex><span><span style=color:#888></span><span> </span></span></span><span style=display:flex><span><span></span><span style=color:#888>Data </span></span></span><span style=display:flex><span><span style=color:#888>==== </span></span></span><span style=display:flex><span><span style=color:#888>default.conf: </span></span></span><span style=display:flex><span><span style=color:#888>---- </span></span></span><span style=display:flex><span><span style=color:#888>server { </span></span></span><span style=display:flex><span><span style=color:#888> listen 80 default_server; </span></span></span><span style=display:flex><span><span style=color:#888> listen [::]:80 default_server ipv6only=on; </span></span></span><span style=display:flex><span><span style=color:#888></span><span> </span></span></span><span style=display:flex><span><span></span><span style=color:#888> listen 443 ssl; </span></span></span><span style=display:flex><span><span style=color:#888></span><span> </span></span></span><span style=display:flex><span><span></span><span style=color:#888> root /usr/share/nginx/html; </span></span></span><span style=display:flex><span><span style=color:#888> index index.html; </span></span></span><span style=display:flex><span><span style=color:#888></span><span> </span></span></span><span style=display:flex><span><span></span><span style=color:#888> server_name localhost; </span></span></span><span style=display:flex><span><span style=color:#888> ssl_certificate /etc/nginx/ssl/tls.crt; </span></span></span><span style=display:flex><span><span style=color:#888> ssl_certificate_key /etc/nginx/ssl/tls.key; </span></span></span><span style=display:flex><span><span style=color:#888></span><span> </span></span></span><span style=display:flex><span><span></span><span style=color:#888> location / { </span></span></span><span style=display:flex><span><span style=color:#888> try_files $uri $uri/ =404; </span></span></span><span style=display:flex><span><span style=color:#888> } </span></span></span><span style=display:flex><span><span style=color:#888>} </span></span></span><span style=display:flex><span><span style=color:#888></span><span> </span></span></span><span style=display:flex><span><span></span><span style=color:#888>BinaryData </span></span></span><span style=display:flex><span><span style=color:#888>==== </span></span></span><span style=display:flex><span><span style=color:#888></span><span> </span></span></span><span style=display:flex><span><span></span><span style=color:#888>Events: <none> </span></span></span></code></pre></div><p>以下是你在运行 make 时遇到问题时要遵循的手动步骤(例如,在 Windows 上):</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 创建公钥和相对应的私钥</span> </span></span><span style=display:flex><span>openssl req -x509 -nodes -days <span style=color:#666>365</span> -newkey rsa:2048 -keyout /d/tmp/nginx.key -out /d/tmp/nginx.crt -subj <span style=color:#b44>"/CN=my-nginx/O=my-nginx"</span> </span></span><span style=display:flex><span><span style=color:#080;font-style:italic># 对密钥实施 base64 编码</span> </span></span><span style=display:flex><span>cat /d/tmp/nginx.crt | base64 </span></span><span style=display:flex><span>cat /d/tmp/nginx.key | base64 </span></span></code></pre></div><p>如下所示,使用上述命令的输出来创建 yaml 文件。base64 编码的值应全部放在一行上。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span><span style=color:#b44>"v1"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span><span style=color:#b44>"Secret"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span><span style=color:#b44>"nginxsecret"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>namespace</span>:<span style=color:#bbb> </span><span style=color:#b44>"default"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>type</span>:<span style=color:#bbb> </span>kubernetes.io/tls<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>data</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>tls.crt</span>:<span style=color:#bbb> </span><span style=color:#b44>"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURIekNDQWdlZ0F3SUJBZ0lKQUp5M3lQK0pzMlpJTUEwR0NTcUdTSWIzRFFFQkJRVUFNQ1l4RVRBUEJnTlYKQkFNVENHNW5hVzU0YzNaak1SRXdEd1lEVlFRS0V3aHVaMmx1ZUhOMll6QWVGdzB4TnpFd01qWXdOekEzTVRKYQpGdzB4T0RFd01qWXdOekEzTVRKYU1DWXhFVEFQQmdOVkJBTVRDRzVuYVc1NGMzWmpNUkV3RHdZRFZRUUtFd2h1CloybHVlSE4yWXpDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBSjFxSU1SOVdWM0IKMlZIQlRMRmtobDRONXljMEJxYUhIQktMSnJMcy8vdzZhU3hRS29GbHlJSU94NGUrMlN5ajBFcndCLzlYTnBwbQppeW1CL3JkRldkOXg5UWhBQUxCZkVaTmNiV3NsTVFVcnhBZW50VWt1dk1vLzgvMHRpbGhjc3paenJEYVJ4NEo5Ci82UVRtVVI3a0ZTWUpOWTVQZkR3cGc3dlVvaDZmZ1Voam92VG42eHNVR0M2QURVODBpNXFlZWhNeVI1N2lmU2YKNHZpaXdIY3hnL3lZR1JBRS9mRTRqakxCdmdONjc2SU90S01rZXV3R0ljNDFhd05tNnNTSzRqYUNGeGpYSnZaZQp2by9kTlEybHhHWCtKT2l3SEhXbXNhdGp4WTRaNVk3R1ZoK0QrWnYvcW1mMFgvbVY0Rmo1NzV3ajFMWVBocWtsCmdhSXZYRyt4U1FVQ0F3RUFBYU5RTUU0d0hRWURWUjBPQkJZRUZPNG9OWkI3YXc1OUlsYkROMzhIYkduYnhFVjcKTUI4R0ExVWRJd1FZTUJhQUZPNG9OWkI3YXc1OUlsYkROMzhIYkduYnhFVjdNQXdHQTFVZEV3UUZNQU1CQWY4dwpEUVlKS29aSWh2Y05BUUVGQlFBRGdnRUJBRVhTMW9FU0lFaXdyMDhWcVA0K2NwTHI3TW5FMTducDBvMm14alFvCjRGb0RvRjdRZnZqeE04Tzd2TjB0clcxb2pGSW0vWDE4ZnZaL3k4ZzVaWG40Vm8zc3hKVmRBcStNZC9jTStzUGEKNmJjTkNUekZqeFpUV0UrKzE5NS9zb2dmOUZ3VDVDK3U2Q3B5N0M3MTZvUXRUakViV05VdEt4cXI0Nk1OZWNCMApwRFhWZmdWQTRadkR4NFo3S2RiZDY5eXM3OVFHYmg5ZW1PZ05NZFlsSUswSGt0ejF5WU4vbVpmK3FqTkJqbWZjCkNnMnlwbGQ0Wi8rUUNQZjl3SkoybFIrY2FnT0R4elBWcGxNSEcybzgvTHFDdnh6elZPUDUxeXdLZEtxaUMwSVEKQ0I5T2wwWW5scE9UNEh1b2hSUzBPOStlMm9KdFZsNUIyczRpbDlhZ3RTVXFxUlU9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>tls.key</span>:<span style=color:#bbb> </span><span style=color:#b44>"LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQ2RhaURFZlZsZHdkbFIKd1V5eFpJWmVEZWNuTkFhbWh4d1NpeWF5N1AvOE9ta3NVQ3FCWmNpQ0RzZUh2dGtzbzlCSzhBZi9WemFhWm9zcApnZjYzUlZuZmNmVUlRQUN3WHhHVFhHMXJKVEVGSzhRSHA3VkpMcnpLUC9QOUxZcFlYTE0yYzZ3MmtjZUNmZitrCkU1bEVlNUJVbUNUV09UM3c4S1lPNzFLSWVuNEZJWTZMMDUrc2JGQmd1Z0ExUE5JdWFubm9UTWtlZTRuMG4rTDQKb3NCM01ZUDhtQmtRQlAzeE9JNHl3YjREZXUraURyU2pKSHJzQmlIT05Xc0RadXJFaXVJMmdoY1kxeWIyWHI2UAozVFVOcGNSbC9pVG9zQngxcHJHclk4V09HZVdPeGxZZmcvbWIvNnBuOUYvNWxlQlkrZStjSTlTMkQ0YXBKWUdpCkwxeHZzVWtGQWdNQkFBRUNnZ0VBZFhCK0xkbk8ySElOTGo5bWRsb25IUGlHWWVzZ294RGQwci9hQ1Zkank4dlEKTjIwL3FQWkUxek1yall6Ry9kVGhTMmMwc0QxaTBXSjdwR1lGb0xtdXlWTjltY0FXUTM5SjM0VHZaU2FFSWZWNgo5TE1jUHhNTmFsNjRLMFRVbUFQZytGam9QSFlhUUxLOERLOUtnNXNrSE5pOWNzMlY5ckd6VWlVZWtBL0RBUlBTClI3L2ZjUFBacDRuRWVBZmI3WTk1R1llb1p5V21SU3VKdlNyblBESGtUdW1vVlVWdkxMRHRzaG9reUxiTWVtN3oKMmJzVmpwSW1GTHJqbGtmQXlpNHg0WjJrV3YyMFRrdWtsZU1jaVlMbjk4QWxiRi9DSmRLM3QraTRoMTVlR2ZQegpoTnh3bk9QdlVTaDR2Q0o3c2Q5TmtEUGJvS2JneVVHOXBYamZhRGR2UVFLQmdRRFFLM01nUkhkQ1pKNVFqZWFKClFGdXF4cHdnNzhZTjQyL1NwenlUYmtGcVFoQWtyczJxWGx1MDZBRzhrZzIzQkswaHkzaE9zSGgxcXRVK3NHZVAKOWRERHBsUWV0ODZsY2FlR3hoc0V0L1R6cEdtNGFKSm5oNzVVaTVGZk9QTDhPTm1FZ3MxMVRhUldhNzZxelRyMgphRlpjQ2pWV1g0YnRSTHVwSkgrMjZnY0FhUUtCZ1FEQmxVSUUzTnNVOFBBZEYvL25sQVB5VWs1T3lDdWc3dmVyClUycXlrdXFzYnBkSi9hODViT1JhM05IVmpVM25uRGpHVHBWaE9JeXg5TEFrc2RwZEFjVmxvcG9HODhXYk9lMTAKMUdqbnkySmdDK3JVWUZiRGtpUGx1K09IYnRnOXFYcGJMSHBzUVpsMGhucDBYSFNYVm9CMUliQndnMGEyOFVadApCbFBtWmc2d1BRS0JnRHVIUVV2SDZHYTNDVUsxNFdmOFhIcFFnMU16M2VvWTBPQm5iSDRvZUZKZmcraEppSXlnCm9RN3hqWldVR3BIc3AyblRtcHErQWlSNzdyRVhsdlhtOElVU2FsbkNiRGlKY01Pc29RdFBZNS9NczJMRm5LQTQKaENmL0pWb2FtZm1nZEN0ZGtFMXNINE9MR2lJVHdEbTRpb0dWZGIwMllnbzFyb2htNUpLMUI3MkpBb0dBUW01UQpHNDhXOTVhL0w1eSt5dCsyZ3YvUHM2VnBvMjZlTzRNQ3lJazJVem9ZWE9IYnNkODJkaC8xT2sybGdHZlI2K3VuCnc1YytZUXRSTHlhQmd3MUtpbGhFZDBKTWU3cGpUSVpnQWJ0LzVPbnlDak9OVXN2aDJjS2lrQ1Z2dTZsZlBjNkQKckliT2ZIaHhxV0RZK2Q1TGN1YSt2NzJ0RkxhenJsSlBsRzlOZHhrQ2dZRUF5elIzT3UyMDNRVVV6bUlCRkwzZAp4Wm5XZ0JLSEo3TnNxcGFWb2RjL0d5aGVycjFDZzE2MmJaSjJDV2RsZkI0VEdtUjZZdmxTZEFOOFRwUWhFbUtKCnFBLzVzdHdxNWd0WGVLOVJmMWxXK29xNThRNTBxMmk1NVdUTThoSDZhTjlaMTltZ0FGdE5VdGNqQUx2dFYxdEYKWSs4WFJkSHJaRnBIWll2NWkwVW1VbGc9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K"</span><span style=color:#bbb> </span></span></span></code></pre></div><p>现在使用文件创建 Secret:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f nginxsecrets.yaml </span></span><span style=display:flex><span>kubectl get secrets </span></span></code></pre></div><pre tabindex=0><code>NAME TYPE DATA AGE nginxsecret kubernetes.io/tls 2 1m </code></pre><p>现在修改 Nginx 副本以启动一个使用 Secret 中的证书的 HTTPS 服务器以及相应的用于暴露其端口(80 和 443)的 Service:</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/service/networking/nginx-secure-app.yaml download=service/networking/nginx-secure-app.yaml><code>service/networking/nginx-secure-app.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("service-networking-nginx-secure-app-yaml")' title="复制 service/networking/nginx-secure-app.yaml 到剪贴板"></img></div><div class=includecode id=service-networking-nginx-secure-app-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Service<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>my-nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>run</span>:<span style=color:#bbb> </span>my-nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>type</span>:<span style=color:#bbb> </span>NodePort<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>port</span>:<span style=color:#bbb> </span><span style=color:#666>8080</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>targetPort</span>:<span style=color:#bbb> </span><span style=color:#666>80</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>protocol</span>:<span style=color:#bbb> </span>TCP<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>http<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>port</span>:<span style=color:#bbb> </span><span style=color:#666>443</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>protocol</span>:<span style=color:#bbb> </span>TCP<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>https<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>run</span>:<span style=color:#bbb> </span>my-nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:#00f;font-weight:700>---</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>apps/v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Deployment<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>my-nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>matchLabels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>run</span>:<span style=color:#bbb> </span>my-nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>replicas</span>:<span style=color:#bbb> </span><span style=color:#666>1</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>template</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>run</span>:<span style=color:#bbb> </span>my-nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumes</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>secret-volume<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>secret</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>secretName</span>:<span style=color:#bbb> </span>nginxsecret<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>configmap-volume<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>configMap</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>nginxconfigmap<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>nginxhttps<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>bprashanth/nginxhttps:1.0<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>containerPort</span>:<span style=color:#bbb> </span><span style=color:#666>443</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>containerPort</span>:<span style=color:#bbb> </span><span style=color:#666>80</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumeMounts</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>mountPath</span>:<span style=color:#bbb> </span>/etc/nginx/ssl<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>secret-volume<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>mountPath</span>:<span style=color:#bbb> </span>/etc/nginx/conf.d<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>configmap-volume<span style=color:#bbb> </span></span></span></code></pre></div></div></div><p>关于 nginx-secure-app 清单,值得注意的几点如下:</p><ul><li>它将 Deployment 和 Service 的规约放在了同一个文件中。</li><li><a href=https://github.com/kubernetes/examples/tree/master/staging/https-nginx/default.conf>Nginx 服务器</a>通过 80 端口处理 HTTP 流量,通过 443 端口处理 HTTPS 流量,而 Nginx Service 则暴露了这两个端口。</li><li>每个容器能通过挂载在 <code>/etc/nginx/ssl</code> 的卷访问密钥。卷和密钥需要在 Nginx 服务器启动 <strong>之前</strong> 配置好。</li></ul><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete deployments,svc my-nginx; kubectl create -f ./nginx-secure-app.yaml </span></span></code></pre></div><p>这时,你可以从任何节点访问到 Nginx 服务器。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods -l <span style=color:#b8860b>run</span><span style=color:#666>=</span>my-nginx -o custom-columns<span style=color:#666>=</span>POD_IP:.status.podIPs </span></span><span style=display:flex><span> POD_IP </span></span><span style=display:flex><span> <span style=color:#666>[</span>map<span style=color:#666>[</span>ip:10.244.3.5<span style=color:#666>]]</span> </span></span></code></pre></div><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>node $ curl -k https://10.244.3.5 </span></span><span style=display:flex><span>... </span></span><span style=display:flex><span><h1>Welcome to nginx!</h1> </span></span></code></pre></div><p>注意最后一步我们是如何提供 <code>-k</code> 参数执行 curl 命令的,这是因为在证书生成时, 我们不知道任何关于运行 nginx 的 Pod 的信息,所以不得不在执行 curl 命令时忽略 CName 不匹配的情况。 通过创建 Service,我们连接了在证书中的 CName 与在 Service 查询时被 Pod 使用的实际 DNS 名字。 让我们从一个 Pod 来测试(为了方便,这里使用同一个 Secret,Pod 仅需要使用 nginx.crt 去访问 Service):</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/service/networking/curlpod.yaml download=service/networking/curlpod.yaml><code>service/networking/curlpod.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("service-networking-curlpod-yaml")' title="复制 service/networking/curlpod.yaml 到剪贴板"></img></div><div class=includecode id=service-networking-curlpod-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>apps/v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Deployment<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>curl-deployment<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>matchLabels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>curlpod<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>replicas</span>:<span style=color:#bbb> </span><span style=color:#666>1</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>template</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>curlpod<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumes</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>secret-volume<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>secret</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>secretName</span>:<span style=color:#bbb> </span>nginxsecret<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>curlpod<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>command</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- sh<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- -c<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- while true; do sleep 1; done<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>radial/busyboxplus:curl<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>volumeMounts</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>mountPath</span>:<span style=color:#bbb> </span>/etc/nginx/ssl<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>secret-volume<span style=color:#bbb> </span></span></span></code></pre></div></div></div><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f ./curlpod.yaml </span></span><span style=display:flex><span>kubectl get pods -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>curlpod </span></span></code></pre></div><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE curl-deployment-1515033274-1410r 1/1 Running 0 1m </code></pre><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl <span style=color:#a2f>exec</span> curl-deployment-1515033274-1410r -- curl https://my-nginx --cacert /etc/nginx/ssl/tls.crt </span></span><span style=display:flex><span>... </span></span><span style=display:flex><span><title>Welcome to nginx!</title> </span></span><span style=display:flex><span>... </span></span></code></pre></div><h2 id=exposing-the-service>暴露 Service</h2><p>对应用的某些部分,你可能希望将 Service 暴露在一个外部 IP 地址上。 Kubernetes 支持两种实现方式:NodePort 和 LoadBalancer。 在上一段创建的 Service 使用了 <code>NodePort</code>,因此,如果你的节点有一个公网 IP,那么 Nginx HTTPS 副本已经能够处理因特网上的流量。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get svc my-nginx -o yaml | grep nodePort -C <span style=color:#666>5</span> </span></span></code></pre></div><pre tabindex=0><code> uid: 07191fb3-f61a-11e5-8ae5-42010af00002 spec: clusterIP: 10.0.162.149 ports: - name: http nodePort: 31704 port: 8080 protocol: TCP targetPort: 80 - name: https nodePort: 32453 port: 443 protocol: TCP targetPort: 443 selector: run: my-nginx </code></pre><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get nodes -o yaml | grep ExternalIP -C <span style=color:#666>1</span> </span></span></code></pre></div><pre tabindex=0><code> - address: 104.197.41.11 type: ExternalIP allocatable: -- - address: 23.251.152.56 type: ExternalIP allocatable: ... $ curl https://<EXTERNAL-IP>:<NODE-PORT> -k ... <h1>Welcome to nginx!</h1> </code></pre><p>让我们重新创建一个 Service 以使用云负载均衡器。 将 <code>my-nginx</code> Service 的 <code>Type</code> 由 <code>NodePort</code> 改成 <code>LoadBalancer</code>:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl edit svc my-nginx </span></span><span style=display:flex><span>kubectl get svc my-nginx </span></span></code></pre></div><pre tabindex=0><code>NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE my-nginx LoadBalancer 10.0.162.149 xx.xxx.xxx.xxx 8080:30163/TCP 21s </code></pre><pre tabindex=0><code>curl https://<EXTERNAL-IP> -k ... <title>Welcome to nginx!</title> </code></pre><p>在 <code>EXTERNAL-IP</code> 列中的 IP 地址能在公网上被访问到。<code>CLUSTER-IP</code> 只能从集群/私有云网络中访问。</p><p>注意,在 AWS 上,类型 <code>LoadBalancer</code> 的服务会创建一个 ELB,且 ELB 使用主机名(比较长),而不是 IP。 ELB 的主机名太长以至于不能适配标准 <code>kubectl get svc</code> 的输出,所以需要通过执行 <code>kubectl describe service my-nginx</code> 命令来查看它。 可以看到类似如下内容:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl describe service my-nginx </span></span><span style=display:flex><span>... </span></span><span style=display:flex><span>LoadBalancer Ingress: a320587ffd19711e5a37606cf4a74574-1142138393.us-east-1.elb.amazonaws.com </span></span><span style=display:flex><span>... </span></span></code></pre></div><h2 id=接下来>接下来</h2><ul><li>进一步了解如何<a href=/zh-cn/docs/tasks/access-application-cluster/service-access-application-cluster/>使用 Service 访问集群中的应用</a></li><li>进一步了解如何<a href=/zh-cn/docs/tasks/access-application-cluster/connecting-frontend-backend/>使用 Service 将前端连接到后端</a></li><li>进一步了解如何<a href=/zh-cn/docs/tasks/access-application-cluster/create-external-load-balancer/>创建外部负载均衡器</a></li></ul></div><div class=td-content style=page-break-before:always><h1 id=pg-5642e8c51749e4fe2e6a2ccc207f1fab>8.2 - 使用源 IP</h1><p>运行在 Kubernetes 集群中的应用程序通过 Service 抽象发现彼此并相互通信,它们也用 Service 与外部世界通信。 本文解释了发送到不同类型 Service 的数据包的源 IP 会发生什么情况,以及如何根据需要切换此行为。</p><h2 id=准备开始>准备开始</h2><h2 id=terminology>术语表</h2><p>本文使用了下列术语:</p><dl><dt><a href=https://zh.wikipedia.org/wiki/%E7%BD%91%E7%BB%9C%E5%9C%B0%E5%9D%80%E8%BD%AC%E6%8D%A2>NAT</a></dt><dd>网络地址转换</dd><dt><a href=https://en.wikipedia.org/wiki/Network_address_translation#SNAT>Source NAT</a></dt><dd>替换数据包上的源 IP;在本页面中,这通常意味着替换为节点的 IP 地址</dd><dt><a href=https://en.wikipedia.org/wiki/Network_address_translation#DNAT>Destination NAT</a></dt><dd>替换数据包上的目标 IP;在本页面中,这通常意味着替换为 <a class=glossary-tooltip title='Pod 表示你的集群上一组正在运行的容器。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/workloads/pods/ target=_blank aria-label=Pod>Pod</a> 的 IP 地址</dd><dt><a href=/zh-cn/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies>VIP</a></dt><dd>一个虚拟 IP 地址,例如分配给 Kubernetes 中每个 <a class=glossary-tooltip title='将运行在一组 Pods 上的应用程序公开为网络服务的抽象方法。' data-toggle=tooltip data-placement=top href=/zh-cn/docs/concepts/services-networking/service/ target=_blank aria-label=Service>Service</a> 的 IP 地址</dd><dt><a href=/zh-cn/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies>Kube-proxy</a></dt><dd>一个网络守护程序,在每个节点上协调 Service VIP 管理</dd></dl><h2 id=prerequisites>先决条件</h2><p>你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 <a href=https://minikube.sigs.k8s.io/docs/tutorials/multi_node/>Minikube</a> 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:</p><ul><li><a href=https://killercoda.com/playgrounds/scenario/kubernetes>Killercoda</a></li><li><a href=https://labs.play-with-k8s.com/>玩转 Kubernetes</a></li></ul><p>示例使用一个小型 nginx Web 服务器,服务器通过 HTTP 标头返回它接收到的请求的源 IP。 你可以按如下方式创建它:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl create deployment source-ip-app --image<span style=color:#666>=</span>registry.k8s.io/echoserver:1.10 </span></span></code></pre></div><p>输出为:</p><pre tabindex=0><code>deployment.apps/source-ip-app created </code></pre><h2 id=教程目标>教程目标</h2><ul><li>通过多种类型的 Service 暴露一个简单应用</li><li>了解每种 Service 类型如何处理源 IP NAT</li><li>了解保留源 IP 所涉及的权衡</li></ul><h2 id=source-ip-for-services-with-type-clusterip><code>Type=ClusterIP</code> 类型 Service 的源 IP</h2><p>如果你在 <a href=/zh-cn/docs/reference/networking/virtual-ips/#proxy-mode-iptables>iptables 模式</a>(默认)下运行 kube-proxy,则从集群内发送到 ClusterIP 的数据包永远不会进行源 NAT。 你可以通过在运行 kube-proxy 的节点上获取 <code>http://localhost:10249/proxyMode</code> 来查询 kube-proxy 模式。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-console data-lang=console><span style=display:flex><span><span style=color:#888>kubectl get nodes </span></span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>NAME STATUS ROLES AGE VERSION kubernetes-node-6jst Ready <none> 2h v1.13.0 kubernetes-node-cx31 Ready <none> 2h v1.13.0 kubernetes-node-jj1t Ready <none> 2h v1.13.0 </code></pre><p>在其中一个节点上获取代理模式(kube-proxy 监听 10249 端口):</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 在要查询的节点上的 Shell 中运行</span> </span></span><span style=display:flex><span>curl http://localhost:10249/proxyMode </span></span></code></pre></div><p>输出为:</p><pre tabindex=0><code>iptables </code></pre><p>你可以通过在源 IP 应用程序上创建 Service 来测试源 IP 保留:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl expose deployment source-ip-app --name<span style=color:#666>=</span>clusterip --port<span style=color:#666>=</span><span style=color:#666>80</span> --target-port<span style=color:#666>=</span><span style=color:#666>8080</span> </span></span></code></pre></div><p>输出为:</p><pre tabindex=0><code>service/clusterip exposed </code></pre><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get svc clusterip </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE clusterip ClusterIP 10.0.170.92 <none> 80/TCP 51s </code></pre><p>并从同一集群中的 Pod 中访问 <code>ClusterIP</code>:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl run busybox -it --image<span style=color:#666>=</span>busybox:1.28 --restart<span style=color:#666>=</span>Never --rm </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>Waiting for pod default/busybox to be running, status is Pending, pod ready: false If you don't see a command prompt, try pressing enter. </code></pre><p>然后,你可以在该 Pod 中运行命令:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 从 “kubectl run” 的终端中运行</span> </span></span><span style=display:flex><span>ip addr </span></span></code></pre></div><pre tabindex=0><code>1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1460 qdisc noqueue link/ether 0a:58:0a:f4:03:08 brd ff:ff:ff:ff:ff:ff inet 10.244.3.8/24 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::188a:84ff:feb0:26a5/64 scope link valid_lft forever preferred_lft forever </code></pre><p>然后使用 <code>wget</code> 查询本地 Web 服务器:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 将 “10.0.170.92” 替换为 Service 中名为 “clusterip” 的 IPv4 地址</span> </span></span><span style=display:flex><span>wget -qO - 10.0.170.92 </span></span></code></pre></div><pre tabindex=0><code>CLIENT VALUES: client_address=10.244.3.8 command=GET ... </code></pre><p>不管客户端 Pod 和服务器 Pod 位于同一节点还是不同节点,<code>client_address</code> 始终是客户端 Pod 的 IP 地址。</p><h2 id=source-ip-for-services-with-type-nodeport><code>Type=NodePort</code> 类型 Service 的源 IP</h2><p>默认情况下,发送到 <a href=/zh-cn/docs/concepts/services-networking/service/#type-nodeport><code>Type=NodePort</code></a> 的 Service 的数据包会经过源 NAT 处理。你可以通过创建一个 <code>NodePort</code> 的 Service 来测试这点:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl expose deployment source-ip-app --name<span style=color:#666>=</span>nodeport --port<span style=color:#666>=</span><span style=color:#666>80</span> --target-port<span style=color:#666>=</span><span style=color:#666>8080</span> --type<span style=color:#666>=</span>NodePort </span></span></code></pre></div><p>输出为:</p><pre tabindex=0><code>service/nodeport exposed </code></pre><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#b8860b>NODEPORT</span><span style=color:#666>=</span><span style=color:#a2f;font-weight:700>$(</span>kubectl get -o <span style=color:#b8860b>jsonpath</span><span style=color:#666>=</span><span style=color:#b44>"{.spec.ports[0].nodePort}"</span> services nodeport<span style=color:#a2f;font-weight:700>)</span> </span></span><span style=display:flex><span><span style=color:#b8860b>NODES</span><span style=color:#666>=</span><span style=color:#a2f;font-weight:700>$(</span>kubectl get nodes -o <span style=color:#b8860b>jsonpath</span><span style=color:#666>=</span><span style=color:#b44>'{ $.items[*].status.addresses[?(@.type=="InternalIP")].address }'</span><span style=color:#a2f;font-weight:700>)</span> </span></span></code></pre></div><p>如果你在云供应商上运行,你可能需要为上面报告的 <code>nodes:nodeport</code> 打开防火墙规则。 现在你可以尝试通过上面分配的节点端口从集群外部访问 Service。</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#a2f;font-weight:700>for</span> node in <span style=color:#b8860b>$NODES</span>; <span style=color:#a2f;font-weight:700>do</span> curl -s <span style=color:#b8860b>$node</span>:<span style=color:#b8860b>$NODEPORT</span> | grep -i client_address; <span style=color:#a2f;font-weight:700>done</span> </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>client_address=10.180.1.1 client_address=10.240.0.5 client_address=10.240.0.3 </code></pre><p>请注意,这些并不是正确的客户端 IP,它们是集群的内部 IP。这是所发生的事情:</p><ul><li>客户端发送数据包到 <code>node2:nodePort</code></li><li><code>node2</code> 使用它自己的 IP 地址替换数据包的源 IP 地址(SNAT)</li><li><code>node2</code> 将数据包上的目标 IP 替换为 Pod IP</li><li>数据包被路由到 node1,然后到端点</li><li>Pod 的回复被路由回 node2</li><li>Pod 的回复被发送回给客户端</li></ul><p>用图表示:</p><figure class=diagram-large><a href=https://mermaid.live/edit#pako:eNqNkV9rwyAUxb-K3LysYEqS_WFYKAzat9GHdW9zDxKvi9RoMIZtlH732ZjSbE970cu5v3s86hFqJxEYfHjRNeT5ZcUtIbXRaMNN2hZ5vrYRqt52cSXV-4iMSuwkZiYtyX739EqWaahMQ-V1qPxDVLNOvkYrO6fj2dupWMR2iiT6foOKdEZoS5Q2hmVSStoH7w7IMqXUVOefWoaG3XVftHbGeZYVRbH6ZXJ47CeL2-qhxvt_ucTe1SUlpuMN6CX12XeGpLdJiaMMFFr0rdAyvvfxjHEIDbbIgcVSohKDCRy4PUV06KQIuJU6OA9MCdMjBTEEt_-2NbDgB7xAGy3i97VJPP0ABRmcqg><img src=/zh-cn/docs/images/tutor-service-nodePort-fig01.svg alt="图 1:源 IP NodePort"></a><figcaption><p>如图。使用 SNAT 的源 IP(Type=NodePort)</p></figcaption></figure><p>为避免这种情况,Kubernetes 有一个特性可以<a href=/zh-cn/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip>保留客户端源 IP</a>。 如果将 <code>service.spec.externalTrafficPolicy</code> 设置为 <code>Local</code>, kube-proxy 只会将代理请求代理到本地端点,而不会将流量转发到其他节点。 这种方法保留了原始源 IP 地址。如果没有本地端点,则发送到该节点的数据包将被丢弃, 因此你可以在任何数据包处理规则中依赖正确的源 IP,你可能会应用一个数据包使其通过该端点。</p><p>设置 <code>service.spec.externalTrafficPolicy</code> 字段如下:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl patch svc nodeport -p <span style=color:#b44>'{"spec":{"externalTrafficPolicy":"Local"}}'</span> </span></span></code></pre></div><p>输出为:</p><pre tabindex=0><code>service/nodeport patched </code></pre><p>现在,重新运行测试:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#a2f;font-weight:700>for</span> node in <span style=color:#b8860b>$NODES</span>; <span style=color:#a2f;font-weight:700>do</span> curl --connect-timeout <span style=color:#666>1</span> -s <span style=color:#b8860b>$node</span>:<span style=color:#b8860b>$NODEPORT</span> | grep -i client_address; <span style=color:#a2f;font-weight:700>done</span> </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>client_address=198.51.100.79 </code></pre><p>请注意,你只从运行端点 Pod 的节点得到了回复,这个回复有<strong>正确的</strong>客户端 IP。</p><p>这是发生的事情:</p><ul><li>客户端将数据包发送到没有任何端点的 <code>node2:nodePort</code></li><li>数据包被丢弃</li><li>客户端发送数据包到<strong>必有</strong>端点的 <code>node1:nodePort</code></li><li>node1 使用正确的源 IP 地址将数据包路由到端点</li></ul><p>用图表示:<figure class=diagram-large><img src=/zh-cn/docs/images/tutor-service-nodePort-fig02.svg alt="图 2:源 IP NodePort"><figcaption><p>如图。源 IP(Type=NodePort)保存客户端源 IP 地址</p></figcaption></figure></p><h2 id=source-ip-for-services-with-type-loadbalancer><code>Type=LoadBalancer</code> 类型 Service 的源 IP</h2><p>默认情况下,发送到 <a href=/zh-cn/docs/concepts/services-networking/service/#loadbalancer><code>Type=LoadBalancer</code></a> 的 Service 的数据包经过源 NAT处理,因为所有处于 <code>Ready</code> 状态的可调度 Kubernetes 节点对于负载均衡的流量都是符合条件的。 因此,如果数据包到达一个没有端点的节点,系统会将其代理到一个<strong>带有</strong>端点的节点,用该节点的 IP 替换数据包上的源 IP(如上一节所述)。</p><p>你可以通过负载均衡器上暴露 source-ip-app 进行测试:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl expose deployment source-ip-app --name<span style=color:#666>=</span>loadbalancer --port<span style=color:#666>=</span><span style=color:#666>80</span> --target-port<span style=color:#666>=</span><span style=color:#666>8080</span> --type<span style=color:#666>=</span>LoadBalancer </span></span></code></pre></div><p>输出为:</p><pre tabindex=0><code>service/loadbalancer exposed </code></pre><p>打印 Service 的 IP 地址:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-console data-lang=console><span style=display:flex><span><span style=color:#888>kubectl get svc loadbalancer </span></span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE loadbalancer LoadBalancer 10.0.65.118 203.0.113.140 80/TCP 5m </code></pre><p>接下来,发送请求到 Service 的 的外部 IP(External-IP):</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>curl 203.0.113.140 </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>CLIENT VALUES: client_address=10.240.0.5 ... </code></pre><p>然而,如果你在 Google Kubernetes Engine/GCE 上运行, 将相同的 <code>service.spec.externalTrafficPolicy</code> 字段设置为 <code>Local</code>, 故意导致健康检查失败,从而强制没有端点的节点把自己从负载均衡流量的可选节点列表中删除。</p><p>用图表示:</p><p><img alt="具有 externalTrafficPolicy 的源 IP" src=/zh-cn/docs/images/sourceip-externaltrafficpolicy.svg></p><p>你可以通过设置注解进行测试:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl patch svc loadbalancer -p <span style=color:#b44>'{"spec":{"externalTrafficPolicy":"Local"}}'</span> </span></span></code></pre></div><p>你应该能够立即看到 Kubernetes 分配的 <code>service.spec.healthCheckNodePort</code> 字段:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get svc loadbalancer -o yaml | grep -i healthCheckNodePort </span></span></code></pre></div><p>输出类似于:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>healthCheckNodePort</span>:<span style=color:#bbb> </span><span style=color:#666>32122</span><span style=color:#bbb> </span></span></span></code></pre></div><p><code>service.spec.healthCheckNodePort</code> 字段指向每个在 <code>/healthz</code> 路径上提供健康检查的节点的端口。你可以这样测试:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pod -o wide -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>source-ip-app </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>NAME READY STATUS RESTARTS AGE IP NODE source-ip-app-826191075-qehz4 1/1 Running 0 20h 10.180.1.136 kubernetes-node-6jst </code></pre><p>使用 <code>curl</code> 获取各个节点上的 <code>/healthz</code> 端点:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 在你选择的节点上本地运行</span> </span></span><span style=display:flex><span>curl localhost:32122/healthz </span></span></code></pre></div><pre tabindex=0><code>1 Service Endpoints found </code></pre><p>在不同的节点上,你可能会得到不同的结果:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span><span style=color:#080;font-style:italic># 在你选择的节点上本地运行</span> </span></span><span style=display:flex><span>curl localhost:32122/healthz </span></span></code></pre></div><pre tabindex=0><code>No Service Endpoints Found </code></pre><p>在<a class=glossary-tooltip title='控制平面是指容器编排层,它暴露 API 和接口来定义、部署容器和管理容器的生命周期。' data-toggle=tooltip data-placement=top href='/zh-cn/docs/reference/glossary/?all=true#term-control-plane' target=_blank aria-label=控制平面>控制平面</a>上运行的控制器负责分配云负载均衡器。 同一个控制器还在每个节点上分配指向此端口/路径的 HTTP 健康检查。 等待大约 10 秒,让 2 个没有端点的节点健康检查失败,然后使用 <code>curl</code> 查询负载均衡器的 IPv4 地址:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>curl 203.0.113.140 </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code>CLIENT VALUES: client_address=198.51.100.79 ... </code></pre><h2 id=cross-platform-support>跨平台支持</h2><p>只有部分云提供商为 <code>Type=LoadBalancer</code> 的 Service 提供保存源 IP 的支持。 你正在运行的云提供商可能会以几种不同的方式满足对负载均衡器的请求:</p><ol><li><p>使用终止客户端连接并打开到你的节点/端点的新连接的代理。 在这种情况下,源 IP 将始终是云 LB 的源 IP,而不是客户端的源 IP。</p></li><li><p>使用数据包转发器,这样客户端发送到负载均衡器 VIP 的请求最终会到达具有客户端源 IP 的节点,而不是中间代理。</p></li></ol><p>第一类负载均衡器必须使用负载均衡器和后端之间商定的协议来传达真实的客户端 IP, 例如 HTTP <a href=https://tools.ietf.org/html/rfc7239#section-5.2>转发</a>或 <a href=https://zh.wikipedia.org/wiki/X-Forwarded-For>X-FORWARDED-FOR</a> 标头,或<a href=https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt>代理协议</a>。 第二类负载均衡器可以通过创建指向存储在 Service 上的 <code>service.spec.healthCheckNodePort</code> 字段中的端口的 HTTP 健康检查来利用上述功能。</p><h2 id=清理现场>清理现场</h2><p>删除 Service:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete svc -l <span style=color:#b8860b>app</span><span style=color:#666>=</span>source-ip-app </span></span></code></pre></div><p>删除 Deployment、ReplicaSet 和 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete deployment source-ip-app </span></span></code></pre></div><h2 id=接下来>接下来</h2><ul><li>详细了解<a href=/zh-cn/docs/tutorials/services/connect-applications-service/>通过 Service 连接应用程序</a></li><li>阅读如何<a href=/zh-cn/docs/tasks/access-application-cluster/create-external-load-balancer/>创建外部负载均衡器</a></li></ul></div><div class=td-content style=page-break-before:always><h1 id=pg-3cecc68ef365a9d2ee0b4860dc74cacc>8.3 - 探索 Pod 及其端点的终止行为</h1><p>一旦你参照<a href=/zh-cn/docs/tutorials/services/connect-applications-service/>使用 Service 连接到应用</a>中概述的那些步骤使用 Service 连接到了你的应用,你就有了一个持续运行的多副本应用暴露在了网络上。 本教程帮助你了解 Pod 的终止流程,探索实现连接排空的几种方式。</p><h2 id=termination-process-for-pods-and-endpoints>Pod 及其端点的终止过程</h2><p>你经常会遇到需要终止 Pod 的场景,例如为了升级或缩容。 为了改良应用的可用性,实现一种合适的活跃连接排空机制变得重要。</p><p>本教程将通过使用一个简单的 nginx Web 服务器演示此概念, 解释 Pod 终止的流程及其与相应端点状态和移除的联系。</p><h2 id=example-flow-with-endpoint-termination>端点终止的示例流程</h2><p>以下是 <a href=/zh-cn/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination>Pod 终止</a>文档中所述的流程示例。</p><p>假设你有包含单个 nginx 副本(仅用于演示目的)的一个 Deployment 和一个 Service:</p><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/service/pod-with-graceful-termination.yaml download=service/pod-with-graceful-termination.yaml><code>service/pod-with-graceful-termination.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("service-pod-with-graceful-termination-yaml")' title="复制 service/pod-with-graceful-termination.yaml 到剪贴板"></img></div><div class=includecode id=service-pod-with-graceful-termination-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>apps/v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Deployment<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>nginx-deployment<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>replicas</span>:<span style=color:#bbb> </span><span style=color:#666>1</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>matchLabels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>template</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>labels</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>terminationGracePeriodSeconds</span>:<span style=color:#bbb> </span><span style=color:#666>120</span><span style=color:#bbb> </span><span style=color:#080;font-style:italic># 超长优雅期</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>containers</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>image</span>:<span style=color:#bbb> </span>nginx:latest<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>containerPort</span>:<span style=color:#bbb> </span><span style=color:#666>80</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>lifecycle</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>preStop</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>exec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:#080;font-style:italic># 实际生产环境中的 Pod 终止可能需要执行任何时长,但不会超过 terminationGracePeriodSeconds。</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:#080;font-style:italic># 在本例中,只需挂起至少 terminationGracePeriodSeconds 所指定的持续时间,</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:#080;font-style:italic># 在 120 秒时容器将被强制终止。</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:#080;font-style:italic># 请注意,在所有这些时间点 nginx 都将继续处理请求。</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>command</span>:<span style=color:#bbb> </span>[<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:#b44>"/bin/sh"</span>,<span style=color:#bbb> </span><span style=color:#b44>"-c"</span>,<span style=color:#bbb> </span><span style=color:#b44>"sleep 180"</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>]<span style=color:#bbb> </span></span></span></code></pre></div></div></div><div class="highlight code-sample"><div class=copy-code-icon><a href=https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/service/explore-graceful-termination-nginx.yaml download=service/explore-graceful-termination-nginx.yaml><code>service/explore-graceful-termination-nginx.yaml</code> </a><img src=/images/copycode.svg class=icon-copycode onclick='copyCode("service-explore-graceful-termination-nginx-yaml")' title="复制 service/explore-graceful-termination-nginx.yaml 到剪贴板"></img></div><div class=includecode id=service-explore-graceful-termination-nginx-yaml><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-yaml data-lang=yaml><span style=display:flex><span><span style=color:green;font-weight:700>apiVersion</span>:<span style=color:#bbb> </span>v1<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>kind</span>:<span style=color:#bbb> </span>Service<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>metadata</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>name</span>:<span style=color:#bbb> </span>nginx-service<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb></span><span style=color:green;font-weight:700>spec</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>selector</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>app</span>:<span style=color:#bbb> </span>nginx<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>ports</span>:<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span>- <span style=color:green;font-weight:700>protocol</span>:<span style=color:#bbb> </span>TCP<span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>port</span>:<span style=color:#bbb> </span><span style=color:#666>80</span><span style=color:#bbb> </span></span></span><span style=display:flex><span><span style=color:#bbb> </span><span style=color:green;font-weight:700>targetPort</span>:<span style=color:#bbb> </span><span style=color:#666>80</span></span></span></code></pre></div></div></div><p>现在使用以上文件创建 Deployment Pod 和 Service:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl apply -f pod-with-graceful-termination.yaml </span></span><span style=display:flex><span>kubectl apply -f explore-graceful-termination-nginx.yaml </span></span></code></pre></div><p>一旦 Pod 和 Service 开始运行,你就可以获取对应的所有 EndpointSlices 的名称:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get endpointslice </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code class=language-none data-lang=none>NAME ADDRESSTYPE PORTS ENDPOINTS AGE nginx-service-6tjbr IPv4 80 10.12.1.199,10.12.1.201 22m </code></pre><p>你可以查看其 status 并验证已经有一个端点被注册:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get endpointslices -o json -l kubernetes.io/service-name<span style=color:#666>=</span>nginx-service </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code class=language-none data-lang=none>{ "addressType": "IPv4", "apiVersion": "discovery.k8s.io/v1", "endpoints": [ { "addresses": [ "10.12.1.201" ], "conditions": { "ready": true, "serving": true, "terminating": false } } ] } </code></pre><p>现在让我们终止这个 Pod 并验证该 Pod 正在遵从体面终止期限的配置进行终止:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl delete pod nginx-deployment-7768647bf9-b4b9s </span></span></code></pre></div><p>查看所有 Pod:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get pods </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code class=language-none data-lang=none>NAME READY STATUS RESTARTS AGE nginx-deployment-7768647bf9-b4b9s 1/1 Terminating 0 4m1s nginx-deployment-7768647bf9-rkxlw 1/1 Running 0 8s </code></pre><p>你可以看到新的 Pod 已被调度。</p><p>当系统在为新的 Pod 创建新的端点时,旧的端点仍处于 Terminating 状态:</p><div class=highlight><pre tabindex=0 style=background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-shell data-lang=shell><span style=display:flex><span>kubectl get endpointslice -o json nginx-service-6tjbr </span></span></code></pre></div><p>输出类似于:</p><pre tabindex=0><code class=language-none data-lang=none>{ "addressType": "IPv4", "apiVersion": "discovery.k8s.io/v1", "endpoints": [ { "addresses": [ "10.12.1.201" ], "conditions": { "ready": false, "serving": true, "terminating": true }, "nodeName": "gke-main-default-pool-dca1511c-d17b", "targetRef": { "kind": "Pod", "name": "nginx-deployment-7768647bf9-b4b9s", "namespace": "default", "uid": "66fa831c-7eb2-407f-bd2c-f96dfe841478" }, "zone": "us-central1-c" }, ] { "addresses": [ "10.12.1.202" ], "conditions": { "ready": true, "serving": true, "terminating": false }, "nodeName": "gke-main-default-pool-dca1511c-d17b", "targetRef": { "kind": "Pod", "name": "nginx-deployment-7768647bf9-rkxlw", "namespace": "default", "uid": "722b1cbe-dcd7-4ed4-8928-4a4d0e2bbe35" }, "zone": "us-central1-c" } } </code></pre><p>这种设计使得应用可以在终止期间公布自己的状态,而客户端(如负载均衡器)则可以实现连接排空功能。 这些客户端可以检测到正在终止的端点,并为这些端点实现特殊的逻辑。</p><p>在 Kubernetes 中,正在终止的端点始终将其 <code>ready</code> 状态设置为 <code>false</code>。 这是为了满足向后兼容的需求,确保现有的负载均衡器不会将 Pod 用于常规流量。 如果需要排空正被终止的 Pod 上的流量,可以将 <code>serving</code> 状况作为实际的就绪状态。</p><p>当 Pod 被删除时,旧的端点也会被删除。</p><h2 id=接下来>接下来</h2><ul><li>了解如何<a href=/zh-cn/docs/tutorials/services/connect-applications-service/>使用 Service 连接到应用</a></li><li>进一步了解<a href=/zh-cn/docs/tasks/access-application-cluster/service-access-application-cluster/>使用 Service 访问集群中的应用</a></li><li>进一步了解<a href=/zh-cn/docs/tasks/access-application-cluster/connecting-frontend-backend/>使用 Service 把前端连接到后端</a></li><li>进一步了解<a href=/zh-cn/docs/tasks/access-application-cluster/create-external-load-balancer/>创建外部负载均衡器</a></li></ul></div></main></div></div><footer class="bg-dark py-5 row d-print-none"><div class="container-fluid mx-sm-5"><div class=row><div class="col-5 col-sm-7 text-center order-2 footer-main"><p><span class=copyright-notice>© 2024 The Kubernetes 作者 | 文档发布基于 <a href=https://git.k8s.io/website/LICENSE class=light-text>CC BY 4.0</a> 授权许可</span></p><p><span class=copyright-notice>© 2024 Linux 基金会®。保留所有权利。Linux 基金会已注册并使用商标。如需了解 Linux 基金会的商标列表,请访问<a href=https://www.linuxfoundation.org/trademark-usage class=light-text>商标使用页面</a></span></p><p><span class=certification-notice>ICP 许可: 京ICP备17074266号-3</span></p></div><div class="col col-sm-2 text-xs-center order-1"><ul class="list-inline mb-0 footer-icons"><li class="list-inline-item mx-2 h3" data-toggle=tooltip data-placement=top title aria-label><a class=text-white target=_blank rel=noopener href=https://youtube.com/kubernetescommunity aria-label><i class="fab fa-youtube"></i></a></li><li class="list-inline-item mx-2 h3" data-toggle=tooltip data-placement=top title aria-label><a class=text-white target=_blank rel=noopener href=https://discuss.kubernetes.io aria-label><i class="fa fa-envelope"></i></a></li><li class="list-inline-item mx-2 h3" data-toggle=tooltip data-placement=top title aria-label><a class=text-white target=_blank rel=noopener href=https://serverfault.com/questions/tagged/kubernetes aria-label><i class="fab fa-stack-overflow"></i></a></li><li class="list-inline-item mx-2 h3" data-toggle=tooltip data-placement=top title aria-label><a class=text-white target=_blank rel=noopener href=https://twitter.com/kubernetesio aria-label><i class="fab fa-twitter"></i></a></li></ul></div><div class="col col-sm-2 text-right text-xs-center order-3"><ul class="list-inline mb-0 footer-icons"><li class="list-inline-item mx-2 h3" data-toggle=tooltip data-placement=top title aria-label><a class=text-white target=_blank rel=noopener href=https://k8s.dev/ aria-label><i class="fas fa-laptop-code"></i></a></li><li class="list-inline-item mx-2 h3" data-toggle=tooltip data-placement=top title aria-label><a class=text-white target=_blank rel=noopener href=https://github.com/kubernetes/kubernetes aria-label><i class="fab fa-github"></i></a></li><li class="list-inline-item mx-2 h3" data-toggle=tooltip data-placement=top title aria-label><a class=text-white target=_blank rel=noopener href=https://slack.k8s.io aria-label><i class="fab fa-slack"></i></a></li><li class="list-inline-item mx-2 h3" data-toggle=tooltip data-placement=top title aria-label><a class=text-white target=_blank rel=noopener href="https://calendar.google.com/calendar/embed?src=calendar%40kubernetes.io" aria-label><i class="fas fa-calendar-alt"></i></a></li></ul></div></div></div></footer></div><script src=/js/jquery-3.6.0.min.js integrity=sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GFKzPxzJdPfGK crossorigin=anonymous></script><script src=/js/popper-1.16.1.min.js intregrity=sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN crossorigin=anonymous></script><script src=/js/bootstrap-4.6.1.min.js integrity=sha384-VHvPCCyXqtD5DqJeNxl2dtTyhF78xXNXdkwX1CZeRusQfRKp+tA7hAShOK/B/fQ2 crossorigin=anonymous></script><script src=/js/script.js></script><script src=/js/main.min.b8b5957f4ba9b582bc3696c8cbed20dde33f789d0fe3255f10fdc7adecf38b7e.js integrity="sha256-uLWVf0uptYK8NpbIy+0g3eM/eJ0P4yVfEP3Hrezzi34=" crossorigin=anonymous></script><script>let splitInstance=null;function enableSplitter(e){e.matches?splitInstance||(splitInstance=Split(["#sidebarnav","#maindoc"],{sizes:[20,80],minSize:100})):splitInstance&&(splitInstance.destroy(),splitInstance=null)}const screenWidthMediaQuery=window.matchMedia("(min-width: 768px)"),eleNav=document.getElementById("sidebarnav");eleNav!==null&&(enableSplitter(screenWidthMediaQuery),screenWidthMediaQuery.addListener(enableSplitter))</script></body></html>