learn.lianglianglee.com/专栏/Kubernetes 从上手到实践/07 集群管理:以 Redis 为例-部署及访问.md.html
2022-05-11 19:04:14 +08:00

676 lines
24 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>07 集群管理:以 Redis 为例-部署及访问.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/Kubernetes 从上手到实践/01 开篇: Kubernetes 是什么以及为什么需要它.md.html">01 开篇: Kubernetes 是什么以及为什么需要它.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 从上手到实践/02 初步认识Kubernetes 基础概念.md.html">02 初步认识Kubernetes 基础概念.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 从上手到实践/03 宏观认识:整体架构.md.html">03 宏观认识:整体架构.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 从上手到实践/04 搭建 Kubernetes 集群 - 本地快速搭建.md.html">04 搭建 Kubernetes 集群 - 本地快速搭建.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 从上手到实践/05 动手实践:搭建一个 Kubernetes 集群 - 生产可用.md.html">05 动手实践:搭建一个 Kubernetes 集群 - 生产可用.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 从上手到实践/06 集群管理:初识 kubectl.md.html">06 集群管理:初识 kubectl.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/Kubernetes 从上手到实践/07 集群管理:以 Redis 为例-部署及访问.md.html">07 集群管理:以 Redis 为例-部署及访问.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 从上手到实践/08 安全重点 认证和授权.md.html">08 安全重点 认证和授权.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 从上手到实践/09 应用发布:部署实际项目.md.html">09 应用发布:部署实际项目.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 从上手到实践/10 应用管理:初识 Helm.md.html">10 应用管理:初识 Helm.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 从上手到实践/11 部署实践:以 Helm 部署项目.md.html">11 部署实践:以 Helm 部署项目.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 从上手到实践/12 庖丁解牛kube-apiserver.md.html">12 庖丁解牛kube-apiserver.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 从上手到实践/13 庖丁解牛etcd.md.html">13 庖丁解牛etcd.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 从上手到实践/14 庖丁解牛controller-manager.md.html">14 庖丁解牛controller-manager.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 从上手到实践/15 庖丁解牛kube-scheduler.md.html">15 庖丁解牛kube-scheduler.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 从上手到实践/16 庖丁解牛kubelet.md.html">16 庖丁解牛kubelet.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 从上手到实践/17 庖丁解牛kube-proxy.md.html">17 庖丁解牛kube-proxy.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 从上手到实践/18 庖丁解牛Container Runtime Docker.md.html">18 庖丁解牛Container Runtime Docker.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 从上手到实践/19 Troubleshoot.md.html">19 Troubleshoot.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 从上手到实践/20 扩展增强Dashboard.md.html">20 扩展增强Dashboard.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 从上手到实践/21 扩展增强CoreDNS.md.html">21 扩展增强CoreDNS.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 从上手到实践/22 服务增强Ingress.md.html">22 服务增强Ingress.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 从上手到实践/23 监控实践:对 K8S 集群进行监控.md.html">23 监控实践:对 K8S 集群进行监控.md.html</a>
</li>
<li>
<a href="/专栏/Kubernetes 从上手到实践/24 总结.md.html">24 总结.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>07 集群管理:以 Redis 为例-部署及访问</h1>
<p>上节我们已经学习了 <code>kubectl</code> 的基础使用,本节我们使用 <code>kubectl</code> 在 K8S 中进行部署。</p>
<p><strong>前面我们已经说过Pod 是 K8S 中最小的调度单元,所以我们无法直接在 K8S 中运行一个 container 但是我们可以运行一个 Pod 而这个 Pod 中只包含一个 container 。</strong></p>
<h2><code>kubectl run</code> 开始</h2>
<p><code>kubectl run</code> 的基础用法如下:</p>
<pre><code>Usage:
kubectl run NAME --image=image [--env=&quot;key=value&quot;] [--port=port] [--replicas=replicas] [--dry-run=bool] [--overrides=inline-json] [--command] -- [COMMAND] [args...] [options]
</code></pre>
<p><code>NAME</code><code>--image</code> 是必需项。分别代表此次部署的名字及所使用的镜像,其余部分之后进行解释。当然,在我们实际使用时,推荐编写配置文件并通过 <code>kubectl create</code> 进行部署。</p>
<h2>使用最小的 Redis 镜像</h2>
<p>在 Redis 的<a href="https://hub.docker.com/_/redis/">官方镜像列表</a>可以看到有很多的 tag 可供选择,其中使用 <a href="https://alpinelinux.org/">Alpine Linux</a> 作为基础的镜像体积最小,下载较为方便。我们选择 <code>redis:alpine</code> 这个镜像进行部署。</p>
<h2>部署</h2>
<p>现在我们只部署一个 Redis 实例。</p>
<pre><code>➜ ~ kubectl run redis --image='redis:alpine'
deployment.apps/redis created
</code></pre>
<p>可以看到提示 <code>deployment.apps/redis created</code> 这个稍后进行解释,我们使用 <code>kubectl get all</code> 来看看到底发生了什么。</p>
<pre><code>➜ ~ kubectl get all
NAME READY STATUS RESTARTS AGE
pod/redis-7c7545cbcb-2m6rp 1/1 Running 0 30s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 &lt;none&gt; 443/TCP 32s
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
deployment.apps/redis 1 1 1 1 30s
NAME DESIRED CURRENT READY AGE
replicaset.apps/redis-7c7545cbcb 1 1 1 30s
</code></pre>
<p>可以看到其中有我们刚才执行 <code>run</code> 操作后创建的 <code>deployment.apps/redis</code>,还有 <code>replicaset.apps/redis-7c7545cbcb</code>, <code>service/kubernetes</code> 以及 <code>pod/redis-7c7545cbcb-f984p</code></p>
<p>使用 <code>kubectl get all</code> 输出内容的格式 <code>/</code> 前代表类型,<code>/</code> 后是名称。</p>
<p>这些分别代表什么含义?</p>
<h3>Deployment</h3>
<p><code>Deployment</code> 是一种高级别的抽象,允许我们进行扩容,滚动更新及降级等操作。我们使用 <code>kubectl run redis --image='redis:alpine</code> 命令便创建了一个名为 <code>redis</code><code>Deployment</code>,并指定了其使用的镜像为 <code>redis:alpine</code></p>
<p>同时 K8S 会默认为其增加一些标签(<code>Label</code>)。我们可以通过更改 <code>get</code> 的输出格式进行查看。</p>
<pre><code>➜ ~ kubectl get deployment.apps/redis -o wide
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
redis 1 1 1 1 40s redis redis:alpine run=redis
➜ ~ kubectl get deploy redis -o wide
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
redis 1 1 1 1 40s redis redis:alpine run=redis
</code></pre>
<p>那么这些 <code>Label</code> 有什么作用呢?它们可作为选择条件进行使用。如:</p>
<pre><code>➜ ~ kubectl get deploy -l run=redis -o wide
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
redis 1 1 1 1 11h redis redis:alpine run=redis
➜ ~ kubectl get deploy -l run=test -o wide # 由于我们并没有创建过 test 所以查不到任何东西
No resources found.
</code></pre>
<p>我们在应用部署或更新时总是会考虑的一个问题是如何平滑升级,利用 <code>Deployment</code> 也能很方便的进行金丝雀发布Canary deployments。这主要也依赖 <code>Label</code><code>Selector</code> 后面我们再详细介绍如何实现。</p>
<p><code>Deployment</code> 的创建除了使用我们这里提到的方式外,更推荐的方式便是使用 <code>yaml</code> 格式的配置文件。在配置文件中主要是声明一种预期的状态,而其他组件则负责协同调度并最终达成这种预期的状态。当然这也是它的关键作用之一,将 <code>Pod</code> 托管给下面将要介绍的 <code>ReplicaSet</code></p>
<h3>ReplicaSet</h3>
<p><code>ReplicaSet</code> 是一种较低级别的结构,允许进行扩容。</p>
<p>我们上面已经提到 <code>Deployment</code> 主要是声明一种预期的状态,并且会将 <code>Pod</code> 托管给 <code>ReplicaSet</code>,而 <code>ReplicaSet</code> 则会去检查当前的 <code>Pod</code> 数量及状态是否符合预期,并尽量满足这一预期。</p>
<p><code>ReplicaSet</code> 可以由我们自行创建,但一般情况下不推荐这样去做,因为如果这样做了,那其实就相当于跳过了 <code>Deployment</code> 的部分,<code>Deployment</code> 所带来的功能或者特性我们便都使用不到了。</p>
<p>除了 <code>ReplicaSet</code> 外,我们还有一个选择名为 <code>ReplicationController</code>,这两者的主要区别更多的在选择器上,我们后面再做讨论。现在推荐的做法是 <code>ReplicaSet</code> 所以不做太多解释。</p>
<p><code>ReplicaSet</code> 可简写为 <code>rs</code>,通过以下命令查看:</p>
<pre><code>➜ ~ kubectl get rs -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
redis-7c7545cbcb 1 1 1 11h redis redis:alpine pod-template-hash=3731017676,run=redis
</code></pre>
<p>在输出结果中,我们注意到这里除了我们前面看到的 <code>run=redis</code> 标签外,还多了一个 <code>pod-template-hash=3731017676</code> 标签,这个标签是由 <code>Deployment controller</code> 自动添加的,目的是为了防止出现重复,所以将 <code>pod-template</code> 进行 hash 用作唯一性标识。</p>
<h3>Service</h3>
<p><code>Service</code> 简单点说就是为了能有个稳定的入口访问我们的应用服务或者是一组 <code>Pod</code>。通过 <code>Service</code> 可以很方便的实现服务发现和负载均衡。</p>
<pre><code>➜ ~ kubectl get service -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 &lt;none&gt; 443/TCP 16m &lt;none&gt;
</code></pre>
<p>通过使用 <code>kubectl</code> 查看,能看到主要会显示 <code>Service</code> 的名称类型IP端口及创建时间和选择器等。我们来具体拆解下。</p>
<h4>类型</h4>
<p><code>Service</code> 目前有 4 种类型:</p>
<ul>
<li><code>ClusterIP</code> 是 K8S 当前默认的 <code>Service</code> 类型。将 service 暴露于一个仅集群内可访问的虚拟 IP 上。</li>
<li><code>NodePort</code> 是通过在集群内所有 <code>Node</code> 上都绑定固定端口的方式将服务暴露出来,这样便可以通过 <code>&lt;NodeIP&gt;:&lt;NodePort&gt;</code> 访问服务了。</li>
<li><code>LoadBalancer</code> 是通过 <code>Cloud Provider</code> 创建一个外部的负载均衡器,将服务暴露出来,并且会自动创建外部负载均衡器路由请求所需的 <code>Nodeport</code><code>ClusterIP</code></li>
<li><code>ExternalName</code> 是通过将服务由 DNS CNAME 的方式转发到指定的域名上将服务暴露出来,这需要 <code>kube-dns</code> 1.7 或更高版本支持。</li>
</ul>
<h4>实践</h4>
<p>上面已经说完了 <code>Service</code> 的基本类型,而我们也已经部署了一个 Redis ,当还无法访问到该服务,接下来我们将刚才部署的 Redis 服务暴露出来。</p>
<pre><code>➜ ~ kubectl expose deploy/redis --port=6379 --protocol=TCP --target-port=6379 --name=redis-server
service/redis-server exposed
➜ ~ kubectl get svc -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 &lt;none&gt; 443/TCP 49m &lt;none&gt;
redis-server ClusterIP 10.108.105.63 &lt;none&gt; 6379/TCP 4s run=redis
</code></pre>
<p>通过 <code>kubectl expose</code> 命令将 redis server 暴露出来,这里需要进行下说明:</p>
<ul>
<li><code>port</code><code>Service</code> 暴露出来的端口,可通过此端口访问 <code>Service</code></li>
<li><code>protocol</code> 是所用协议。当前 K8S 支持 TCP/UDP 协议,在 1.12 版本中实验性的加入了对 <a href="https://zh.wikipedia.org/zh-hans/流控制传输协议">SCTP 协议</a>的支持。默认是 TCP 协议。</li>
<li><code>target-port</code> 是实际服务所在的目标端口,请求由 <code>port</code> 进入通过上述指定 <code>protocol</code> 最终流向这里配置的端口。</li>
<li><code>name</code> <code>Service</code> 的名字,它的用处主要在 dns 方面。</li>
<li><code>type</code> 是前面提到的类型,如果没指定默认是 <code>ClusterIP</code></li>
</ul>
<p>现在我们的 redis 是使用的默认类型 <code>ClusterIP</code>,所以并不能直接通过外部进行访问,我们使用 <code>port-forward</code> 的方式让它可在集群外部访问。</p>
<pre><code>➜ ~ kubectl port-forward svc/redis-server 6379:6379
Forwarding from 127.0.0.1:6379 -&gt; 6379
Forwarding from [::1]:6379 -&gt; 6379
Handling connection for 6379
</code></pre>
<p>在另一个本地终端内可通过 redis-cli 工具进行连接:</p>
<pre><code>➜ ~ redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379&gt; ping
PONG
</code></pre>
<p>当然,我们也可以使用 <code>NodePort</code> 的方式对外暴露服务。</p>
<pre><code>➜ ~ kubectl expose deploy/redis --port=6379 --protocol=TCP --target-port=6379 --name=redis-server-nodeport --type=NodePort
service/redis-server-nodeport exposed
➜ ~ kubectl get service/redis-server-nodeport -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
redis-server-nodeport NodePort 10.109.248.204 &lt;none&gt; 6379:31913/TCP 11s run=redis
</code></pre>
<p>我们可以通过任意 <code>Node</code> 上的 31913 端口便可连接我们的 redis 服务。当然,这里需要注意的是这个端口范围其实是可以通过 <code>kube-apiserver</code><code>service-node-port-range</code> 进行配置的,默认是 <code>30000-32767</code></p>
<h3>Pod</h3>
<p>第二节中,我们提到过 <code>Pod</code> 是 K8S 中的最小化部署单元。我们看下当前集群中 <code>Pod</code> 的状态。</p>
<pre><code>➜ ~ kubectl get pods
NAME READY STATUS RESTARTS AGE
redis-7c7545cbcb-jwcf2 1/1 Running 0 8h
</code></pre>
<p>我们进行一次简单的扩容操作。</p>
<pre><code>➜ ~ kubectl scale deploy/redis --replicas=2
deployment.extensions/redis scaled
➜ ~ kubectl get pods
NAME READY STATUS RESTARTS AGE
redis-7c7545cbcb-jwcf2 1/1 Running 0 8h
redis-7c7545cbcb-wzh6w 1/1 Running 0 4s
</code></pre>
<p>可以看到 <code>Pod</code> 数已经增加,并且也已经是 <code>Running</code> 的状态了。(当然在生产环境中 Redis 服务的扩容并不是使用这种方式进行扩容的,需要看实际的部署方式以及业务的使用姿势。)</p>
<h2>总结</h2>
<p>本节我们使用 Redis 作为例子,学习了集群管理相关的基础知识。学习了如何进行应用部署, <code>Service</code> 的基础类型以及如何通过 <code>port-forward</code><code>NodePort</code> 等方式将服务提供至集群的外部访问。</p>
<p>同时我们学习了应用部署中主要会涉及到的几类资源 <code>Deployment</code><code>Replicaset</code><code>Service</code><code>Pod</code> 等。对这些资源及它们之间关系的掌握,对于后续集群维护或定位问题有很大的帮助。</p>
<p>下节,我们开始学习在生产环境中使用 K8S 至关重要的一环,权限控制。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Kubernetes 从上手到实践/06 集群管理:初识 kubectl.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/Kubernetes 从上手到实践/08 安全重点 认证和授权.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997238acd43d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>