Files
David Young d0d428f3bf build site
2026-05-14 14:23:58 -06:00

971 lines
41 KiB
HTML
Raw Permalink 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>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script>
(function() {
const autoTheme = false;
if (autoTheme) {
document.documentElement.setAttribute('data-auto-theme', 'true');
}
const theme = localStorage.getItem('cleanwhite-theme') ||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', theme);
})();
</script>
<meta property="og:site_name" content="David Young">
<meta property="og:type" content="article">
<meta property="og:image" content="https://davidpaulyoung.com/https://img.zhaohuabing.com/post-bg-2015.jpg">
<meta property="twitter:image" content="https://davidpaulyoung.com/https://img.zhaohuabing.com/post-bg-2015.jpg" />
<meta name="title" content="如何从外部访问Kubernetes集群中的应用" />
<meta property="og:title" content="如何从外部访问Kubernetes集群中的应用" />
<meta property="twitter:title" content="如何从外部访问Kubernetes集群中的应用" />
<meta name="description" content="我们知道kubernetes的Cluster Network属于私有网络只能在cluster Network内部才能访问部署的应用那如何才能将Kubernetes集群中的应用暴露到外部网络为外部用户提供服务呢本文探讨了从外部网络访问kubernetes cluster中应用的几种实现方式。">
<meta property="og:description" content="我们知道kubernetes的Cluster Network属于私有网络只能在cluster Network内部才能访问部署的应用那如何才能将Kubernetes集群中的应用暴露到外部网络为外部用户提供服务呢本文探讨了从外部网络访问kubernetes cluster中应用的几种实现方式。" />
<meta property="twitter:description" content="我们知道kubernetes的Cluster Network属于私有网络只能在cluster Network内部才能访问部署的应用那如何才能将Kubernetes集群中的应用暴露到外部网络为外部用户提供服务呢本文探讨了从外部网络访问kubernetes cluster中应用的几种实现方式。" />
<meta property="og:url" content="https://davidpaulyoung.com/2017/11/28/access-application-from-outside/" />
<meta property="twitter:card" content="summary" />
<meta name="keyword" content="Von Balthasar, Scripture, Gravel Riding, Ham Radio, Divine Office, Open Source">
<link rel="shortcut icon" href="/img/favicon.ico">
<title>如何从外部访问Kubernetes集群中的应用 | David Young Blog</title>
<link rel="canonical" href="/2017/11/28/access-application-from-outside/">
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/hugo-theme-cleanwhite.css">
<link rel="stylesheet" href="/css/theme-variables.css">
<link rel="stylesheet" href="/css/zanshang.min.css">
<link rel="stylesheet" href="/css/font-awesome.all.min.css">
<script src="/js/jquery.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<script src="/js/hux-blog.min.js"></script>
<script src="/js/lazysizes.min.js"></script>
</head>
<nav class="navbar navbar-default navbar-custom navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header page-scroll">
<button type="button" class="navbar-toggle">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">David Young</a>
</div>
<div id="huxblog_navbar">
<div class="navbar-collapse">
<ul class="nav navbar-nav navbar-right">
<li>
<a href="/">All Posts</a>
</li>
<li>
<a href="/categories/life/">life</a>
</li>
<li>
<a href="/categories/tech/">tech</a>
</li>
<li>
<a href="/categories/tips/">tips</a>
</li>
<li><a href="/archive//">ARCHIVE</a></li>
<li><a href="/notes//">NOTES</a></li>
<li><a href="/about//">ABOUT</a></li>
<li>
<a href="/search"><i class="fa fa-search"></i></a>
</li>
<li>
<a href="#" id="theme-toggle" title="Toggle dark mode" style="opacity: 0;">
<i class="fa fa-moon"></i>
<i class="fa fa-sun" style="display: none;"></i>
</a>
</li>
<script>
(function() {
var theme = localStorage.getItem('cleanwhite-theme') ||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
var toggleBtn = document.getElementById('theme-toggle');
if (toggleBtn) {
var moonIcon = toggleBtn.querySelector('.fa-moon');
var sunIcon = toggleBtn.querySelector('.fa-sun');
if (theme === 'dark') {
if (moonIcon) moonIcon.style.display = 'none';
if (sunIcon) sunIcon.style.display = 'inline';
toggleBtn.setAttribute('title', 'Switch to light mode');
} else {
if (moonIcon) moonIcon.style.display = 'inline';
if (sunIcon) sunIcon.style.display = 'none';
toggleBtn.setAttribute('title', 'Switch to dark mode');
}
requestAnimationFrame(function() {
toggleBtn.style.transition = 'opacity 0.2s ease';
toggleBtn.style.opacity = '1';
});
}
})();
</script>
</ul>
</div>
</div>
</div>
</nav>
<script>
var $body = document.body;
var $toggle = document.querySelector('.navbar-toggle');
var $navbar = document.querySelector('#huxblog_navbar');
var $collapse = document.querySelector('.navbar-collapse');
$toggle.addEventListener('click', handleMagic)
function handleMagic(e){
if ($navbar.className.indexOf('in') > 0) {
$navbar.className = " ";
setTimeout(function(){
if($navbar.className.indexOf('in') < 0) {
$collapse.style.height = "0px"
}
},400)
}else{
$collapse.style.height = "auto"
$navbar.className += " in";
}
}
document.addEventListener('DOMContentLoaded', function() {
var navLinks = document.querySelectorAll('.navbar-collapse a');
navLinks.forEach(function(link) {
link.addEventListener('click', function() {
if ($navbar.className.indexOf('in') > 0) {
$navbar.className = " ";
setTimeout(function(){
if($navbar.className.indexOf('in') < 0) {
$collapse.style.height = "0px"
}
},400)
}
});
});
});
</script>
<style type="text/css">
header.intro-header {
background-image: url('https://img.zhaohuabing.com/post-bg-2015.jpg')
}
</style>
<header class="intro-header" >
<div class="container">
<div class="row">
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
<div class="post-heading">
<div class="tags">
<a class="tag" href="/tags/kubernetes" title="Kubernetes">
Kubernetes
</a>
</div>
<h1>如何从外部访问Kubernetes集群中的应用</h1>
<h2 class="subheading"></h2>
<span class="meta">
Posted by
赵化冰
on
Tuesday, November 28, 2017
</span>
</div>
</div>
</div>
</div>
</header>
<article>
<div class="container">
<div class="row">
<div class="
col-lg-8 col-lg-offset-2
col-md-10 col-md-offset-1
post-container">
<h2 id="前言">前言</h2>
<p>我们知道kubernetes的Cluster Network属于私有网络只能在cluster Network内部才能访问部署的应用那如何才能将Kubernetes集群中的应用暴露到外部网络为外部用户提供服务呢本文探讨了从外部网络访问kubernetes cluster中应用的几种实现方式。</p>
<blockquote>
<p>本文尽量试着写得比较容易理解但要做到“深入浅出”把复杂的事情用通俗易懂的语言描述出来是非常需要功力的个人自认尚未达到此境界唯有不断努力。此外kubernetes本身是一个比较复杂的系统无法在本文中详细解释涉及的所有相关概念否则就可能脱离了文章的主题因此假设阅读此文之前读者对kubernetes的基本概念如dockercontainerpod已有所了解。</p>
</blockquote>
<p>另外此文中的一些内容是自己的理解,由于个人的知识范围有限,可能有误,如果读者对文章中的内容有疑问或者勘误,欢迎大家指证。</p>
<h2 id="pod和service">Pod和Service</h2>
<p>我们首先来了解一下Kubernetes中的Pod和Service的概念。</p>
<p>Pod(容器组),英文中Pod是豆荚的意思从名字的含义可以看出Pod是一组有依赖关系的容器Pod包含的容器都会运行在同一个host节点上共享相同的volumes和network namespace空间。Kubernetes以Pod为基本操作单元可以同时启动多个相同的pod用于failover或者load balance。</p>
<p>
<img src="/img/access-application-from-outside/pod.PNG" alt="Pod">
</p>
<p>Pod的生命周期是短暂的Kubernetes根据应用的配置会对Pod进行创建销毁根据监控指标进行缩扩容。kubernetes在创建Pod时可以选择集群中的任何一台空闲的Host因此其网络地址是不固定的。由于Pod的这一特点一般不建议直接通过Pod的地址去访问应用。</p>
<p>为了解决访问Pod不方便直接访问的问题Kubernetes采用了Service的概念Service是对后端提供服务的一组Pod的抽象Service会绑定到一个固定的虚拟IP上该虚拟IP只在Kubernetes Cluster中可见但其实该IP并不对应一个虚拟或者物理设备而只是IPtable中的规则然后再通过IPtable将服务请求路由到后端的Pod中。通过这种方式可以确保服务消费者可以稳定地访问Pod提供的服务而不用关心Pod的创建、删除、迁移等变化以及如何用一组Pod来进行负载均衡。</p>
<p>Service的机制如下图所示Kube-proxy监听kubernetes master增加和删除Service以及Endpoint的消息对于每一个Servicekube proxy创建相应的iptables规则将发送到Service Cluster IP的流量转发到Service后端提供服务的Pod的相应端口上。
<img src="/img/access-application-from-outside/services-iptables-overview.png" alt="Pod和Service的关系">
</p>
<blockquote>
<p>备注虽然可以通过Service的Cluster IP和服务端口访问到后端Pod提供的服务但该Cluster IP是Ping不通的原因是Cluster IP只是iptable中的规则并不对应到一个网络设备。</p>
</blockquote>
<h2 id="service的类型">Service的类型</h2>
<p>Service的类型(ServiceType)决定了Service如何对外提供服务根据类型不同服务可以只在Kubernetes cluster中可见也可以暴露到Cluster外部。Service有三种类型ClusterIPNodePort和LoadBalancer。其中ClusterIP是Service的缺省类型这种类型的服务会提供一个只能在Cluster内才能访问的虚拟IP其实现机制如上面一节所述。</p>
<h2 id="通过nodeport提供外部访问入口">通过NodePort提供外部访问入口</h2>
<p>通过将Service的类型设置为NodePort可以在Cluster中的主机上通过一个指定端口暴露服务。注意通过Cluster中每台主机上的该指定端口都可以访问到该服务发送到该主机端口的请求会被kubernetes路由到提供服务的Pod上。采用这种服务类型可以在kubernetes cluster网络外通过主机IP端口的方式访问到服务。</p>
<blockquote>
<p>注意官方文档中说明了Kubernetes clusterIp的流量转发到后端Pod有Iptable和kube proxy两种方式。但对Nodeport如何转发流量却语焉不详。该图来自网络从图来看是通过kube proxy转发的我没有去研究过源码。欢迎了解的同学跟帖说明。</p>
</blockquote>
<p>
<img src="/img/access-application-from-outside/nodeport.PNG" alt="Pod和Service的关系">
</p>
<p>下面是通过NodePort向外暴露服务的一个例子注意可以指定一个nodePort也可以不指定。在不指定的情况下kubernetes会从可用的端口范围内自动分配一个随机端口。</p>
<pre tabindex="0"><code>kind: Service
apiVersion: v1
metadata:
name: influxdb
spec:
type: NodePort
ports:
- port: 8086
nodePort: 30000
selector:
name: influxdb
</code></pre><p>通过NodePort从外部访问有下面的一些问题自己玩玩或者进行测试时可以使用该方案但不适宜用于生产环境。</p>
<ul>
<li>
<p>Kubernetes cluster host的IP必须是一个well-known IP即客户端必须知道该IP。但Cluster中的host是被作为资源池看待的可以增加删除每个host的IP一般也是动态分配的因此并不能认为host IP对客户端而言是well-known IP。</p>
</li>
<li>
<p>客户端访问某一个固定的host IP存在单点故障。假如一台host宕机了kubernetes cluster会把应用 reload到另一节点上但客户端就无法通过该host的nodeport访问应用了。</p>
</li>
<li>
<p>该方案假设客户端可以访问Kubernetes host所在网络。在生产环境中客户端和Kubernetes host网络可能是隔离的。例如客户端可能是公网中的一个手机APP是无法直接访问host所在的私有网络的。</p>
</li>
</ul>
<p>因此需要通过一个网关来将外部客户端的请求导入到Cluster中的应用中在kubernetes中这个网关是一个4层的load balancer。</p>
<h2 id="通过load-balancer提供外部访问入口">通过Load Balancer提供外部访问入口</h2>
<p>通过将Service的类型设置为LoadBalancer可以为Service创建一个外部Load Balancer。Kubernetes的文档中声明该Service类型需要云服务提供商的支持其实这里只是在Kubernetes配置文件中提出了一个要求即为该Service创建Load Balancer至于如何创建则是由Google Cloud或Amazon Cloud等云服务商提供的创建的Load Balancer不在Kubernetes Cluster的管理范围中。kubernetes 1.6版本中WS, Azure, CloudStack, GCE and OpenStack等云提供商已经可以为Kubernetes提供Load Balancer.下面是一个Load balancer类型的Service例子</p>
<pre tabindex="0"><code>kind: Service
apiVersion: v1
metadata:
name: influxdb
spec:
type: LoadBalancer
ports:
- port: 8086
selector:
name: influxdb
</code></pre><p>部署该Service后我们来看一下Kubernetes创建的内容</p>
<pre tabindex="0"><code>$ kubectl get svc influxdb
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
influxdb 10.97.121.42 10.13.242.236 8086:30051/TCP 39s
</code></pre><p>Kubernetes首先为influxdb创建了一个集群内部可以访问的ClusterIP 10.97.121.42。由于没有指定nodeport端口kubernetes选择了一个空闲的30051主机端口将service暴露在主机的网络上然后通知cloud provider创建了一个load balancer上面输出中的EEXTERNAL-IP就是load balancer的IP。</p>
<p>测试使用的Cloud Provider是OpenStack我们通过neutron lb-vip-show可以查看创建的Load Balancer详细信息。</p>
<pre tabindex="0"><code>$ neutron lb-vip-show 9bf2a580-2ba4-4494-93fd-9b6969c55ac3
+---------------------+--------------------------------------------------------------+
| Field | Value |
+---------------------+--------------------------------------------------------------+
| address | 10.13.242.236 |
| admin_state_up | True |
| connection_limit | -1 |
| description | Kubernetes external service a6ffa4dadf99711e68ea2fa163e0b082 |
| id | 9bf2a580-2ba4-4494-93fd-9b6969c55ac3 |
| name | a6ffa4dadf99711e68ea2fa163e0b082 |
| pool_id | 392917a6-ed61-4924-acb2-026cd4181755 |
| port_id | e450b80b-6da1-4b31-a008-280abdc6400b |
| protocol | TCP |
| protocol_port | 8086 |
| session_persistence | |
| status | ACTIVE |
| status_description | |
| subnet_id | 73f8eb91-90cf-42f4-85d0-dcff44077313 |
| tenant_id | 4d68886fea6e45b0bc2e05cd302cccb9 |
+---------------------+--------------------------------------------------------------+
$ neutron lb-pool-show 392917a6-ed61-4924-acb2-026cd4181755
+------------------------+--------------------------------------+
| Field | Value |
+------------------------+--------------------------------------+
| admin_state_up | True |
| description | |
| health_monitors | |
| health_monitors_status | |
| id | 392917a6-ed61-4924-acb2-026cd4181755 |
| lb_method | ROUND_ROBIN |
| members | d0825cc2-46a3-43bd-af82-e9d8f1f85299 |
| | 3f73d3bb-bc40-478d-8d0e-df05cdfb9734 |
| name | a6ffa4dadf99711e68ea2fa163e0b082 |
| protocol | TCP |
| provider | haproxy |
| status | ACTIVE |
| status_description | |
| subnet_id | 73f8eb91-90cf-42f4-85d0-dcff44077313 |
| tenant_id | 4d68886fea6e45b0bc2e05cd302cccb9 |
| vip_id | 9bf2a580-2ba4-4494-93fd-9b6969c55ac3 |
+------------------------+--------------------------------------+
$ neutron lb-member-list
+--------------------------------------+--------------+---------------+--------+----------------+--------+
| id | address | protocol_port | weight | admin_state_up | status |
+--------------------------------------+--------------+---------------+--------+----------------+--------+
| 3f73d3bb-bc40-478d-8d0e-df05cdfb9734 | 10.13.241.89 | 30051 | 1 | True | ACTIVE |
| d0825cc2-46a3-43bd-af82-e9d8f1f85299 | 10.13.241.10 | 30051 | 1 | True | ACTIVE |
+--------------------------------------+--------------+---------------+--------+----------------+--------
</code></pre><p>可以看到OpenStack使用VIP 10.13.242.236在端口8086创建了一个Load BalancerLoad Balancer对应的Lb pool里面有两个成员10.13.241.89 和 10.13.241.10正是Kubernetes的host节点进入Load balancer流量被分发到这两个节点对应的Service Nodeport 30051上。</p>
<p>但是如果客户端不在Openstack Neutron的私有子网上则还需要在load balancer的VIP上关联一个floating IP以使外部客户端可以连接到load balancer。</p>
<p>部署Load balancer后应用的拓扑结构如下图所示本图假设Kubernetes Cluster部署在Openstack私有云上
<img src="/img/access-application-from-outside/load-balancer.PNG" alt="外部Load balancer">
</p>
<blockquote>
<p>备注如果kubernetes环境在Public Cloud上Loadbalancer类型的Service创建出的外部Load Balancer已经带有公网IP地址是可以直接从外部网络进行访问的不需要绑定floating IP这个步骤。例如在AWS上创建的Elastic Load Balancing (ELB),有兴趣可以看一下这篇文章:<a href="http://docs.heptio.com/content/tutorials/aws-qs-services-elb.html">Expose Services on your AWS Quick Start Kubernetes cluster</a></p>
</blockquote>
<p>如果Kubernetes Cluster是在不支持LoadBalancer特性的cloud provider或者裸机上创建的可以实现LoadBalancer类型的Service吗应该也是可以的。Kubernetes本身并不直接支持Loadbalancer但我们可以通过对Kubernetes进行扩展来实现可以监听kubernetes Master的service创建消息并根据消息部署相应的Load Balancer如Nginx或者HAProxy来实现Load balancer类型的Service。</p>
<p>通过设置Service类型提供的是四层Load Balancer当只需要向外暴露一个服务的时候可以直接采用这种方式。但在一个应用需要对外提供多个服务时采用该方式会为每一个服务IP+Port都创建一个外部load balancer。如下图所示
<img src="/img/access-application-from-outside/multiple-load-balancer.PNG" alt="创建多个Load balancer暴露应用的多个服务">
一般来说,同一个应用的多个服务/资源会放在同一个域名下在这种情况下创建多个Load balancer是完全没有必要的反而带来了额外的开销和管理成本。直接将服务暴露给外部用户也会导致了前端和后端的耦合影响了后端架构的灵活性如果以后由于业务需求对服务进行调整会直接影响到客户端。可以通过使用Kubernetes Ingress进行L7 load balancing来解决该问题。</p>
<h2 id="采用ingress作为七层load-balancer">采用Ingress作为七层load balancer</h2>
<p>首先看一下引入Ingress后的应用拓扑示意图本图假设Kubernetes Cluster部署在Openstack私有云上
<img src="/img/access-application-from-outside/ingress.PNG" alt="采用Ingress暴露应用的多个服务">
这里Ingress起到了七层负载均衡器和Http方向代理的作用可以根据不同的url把入口流量分发到不同的后端Service。外部客户端只看到foo.bar.com这个服务器屏蔽了内部多个Service的实现方式。采用这种方式简化了客户端的访问并增加了后端实现和部署的灵活性可以在不影响客户端的情况下对后端的服务部署进行调整。</p>
<p>下面是Kubernetes Ingress配置文件的示例在虚拟主机foot.bar.com下面定义了两个Path其中/foo被分发到后端服务s1/bar被分发到后端服务s2。</p>
<pre tabindex="0"><code>apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: test
annotations:
ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: foo.bar.com
http:
paths:
- path: /foo
backend:
serviceName: s1
servicePort: 80
- path: /bar
backend:
serviceName: s2
servicePort: 80
</code></pre><p>注意这里Ingress只描述了一个虚拟主机路径分发的要求可以定义多个Ingress描述不同的7层分发要求而这些要求需要由一个Ingress Controller来实现。Ingress Contorller会监听Kubernetes Master得到Ingress的定义并根据Ingress的定义对一个7层代理进行相应的配置以实现Ingress定义中要求的虚拟主机和路径分发规则。Ingress Controller有多种实现Kubernetes提供了一个<a href="https://github.com/kubernetes/ingress-nginx">基于Nginx的Ingress Controller</a>。需要注意的是在部署Kubernetes集群时并不会缺省部署Ingress Controller需要我们自行部署。</p>
<p>下面是部署Nginx Ingress Controller的配置文件示例注意这里为Nginx Ingress Controller定义了一个LoadBalancer类型的Service以为Ingress Controller提供一个外部可以访问的公网IP。</p>
<pre tabindex="0"><code>apiVersion: v1
kind: Service
metadata:
name: nginx-ingress
spec:
type: LoadBalancer
ports:
- port: 80
name: http
- port: 443
name: https
selector:
k8s-app: nginx-ingress-lb
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-ingress-controller
spec:
replicas: 2
revisionHistoryLimit: 3
template:
metadata:
labels:
k8s-app: nginx-ingress-lb
spec:
terminationGracePeriodSeconds: 60
containers:
- name: nginx-ingress-controller
image: gcr.io/google_containers/nginx-ingress-controller:0.8.3
imagePullPolicy: Always
//----omitted for brevity----
</code></pre><blockquote>
<p>备注Google Cloud直接支持Ingress资源如果应用部署在Google Cloud中Google Cloud会自动为Ingress资源创建一个7层load balancer并为之分配一个外部IP不需要自行部署Ingress Controller。</p>
</blockquote>
<h2 id="结论">结论</h2>
<p>采用Ingress加上Load balancer的方式可以将Kubernetes Cluster中的应用服务暴露给外部客户端。这种方式比较灵活基本可以满足大部分应用的需要。但如果需要在入口处提供更强大的功能如有更高的效率要求需求进行安全认证日志记录或者需要一些应用的定制逻辑则需要考虑采用微服务架构中的API Gateway模式采用一个更强大的API Gateway来作为应用的流量入口。</p>
<h2 id="参考">参考</h2>
<ul>
<li>
<p><a href="http://alesnosek.com/blog/2017/02/14/accessing-kubernetes-pods-from-outside-of-the-cluster/">Accessing Kubernetes Pods from Outside of the Cluster</a></p>
</li>
<li>
<p><a href="https://daemonza.github.io/2017/02/13/kubernetes-nginx-ingress-controller/">Kubernetes nginx-ingress-controller</a></p>
</li>
<li>
<p><a href="https://docs.openstack.org/magnum/ocata/dev/kubernetes-load-balancer.html">Using Kubernetes external load balancer feature</a></p>
</li>
<li>
<p><a href="http://docs.heptio.com/content/tutorials/aws-qs-services-elb.html">Expose Services on your AWS Quick Start Kubernetes cluster</a></p>
</li>
</ul>
<div class="entry-shang text-center">
<p>「真诚赞赏,手留余香」</p>
<button class="zs show-zs btn btn-bred">赞赏支持</button>
</div>
<div class="zs-modal-bg"></div>
<div class="zs-modal-box">
<div class="zs-modal-head">
<button type="button" class="close">×</button>
<span class="author"><a href="https://davidpaulyoung.com/"><img src="/img/favicon.png" />David Young</a></span>
<p class="tip"><i></i><span>真诚赞赏,手留余香</span></p>
</div>
<div class="zs-modal-body">
<div class="zs-modal-btns">
<button class="btn btn-blink" data-num="2">2元</button>
<button class="btn btn-blink" data-num="5">5元</button>
<button class="btn btn-blink" data-num="10">10元</button>
<button class="btn btn-blink" data-num="50">50元</button>
<button class="btn btn-blink" data-num="100">100元</button>
<button class="btn btn-blink" data-num="1">任意金额</button>
</div>
<div class="zs-modal-pay">
<button class="btn btn-bred" id="pay-text">2元</button>
<p>使用<span id="pay-type">微信</span>扫描二维码完成支付</p>
<img src="/img/reward/wechat-2.png" id="pay-image"/>
</div>
</div>
<div class="zs-modal-footer">
<label><input type="radio" name="zs-type" value="wechat" class="zs-type" checked="checked"><span ><span class="zs-wechat"><img src="/img/reward/wechat-btn.png"/></span></label>
<label><input type="radio" name="zs-type" value="alipay" class="zs-type" class="zs-alipay"><img src="/img/reward/alipay-btn.png"/></span></label>
</div>
</div>
<script type="text/javascript" src="/js/reward.js"></script>
<hr>
<ul class="pager">
<li class="previous">
<a href="/2017/11/08/istio-canary-release/" data-toggle="tooltip" data-placement="top" title="采用Istio实现灰度发布(金丝雀发布)">&larr;
Previous Post</a>
</li>
<li class="next">
<a href="/2018/01/02/nginmesh-install/" data-toggle="tooltip" data-placement="top" title="Nginx开源Service Mesh组件Nginmesh安装指南">Next
Post &rarr;</a>
</li>
</ul>
<link href="https://xxx.xxx.com/dist/Artalk.css" rel="stylesheet" />
<script src="https://xxx.xxx.com/dist/Artalk.js"></script>
<div id="Comments"></div>
<script>
Artalk.init({
el: '#Comments',
pageKey: 'https:\/\/davidpaulyoung.com\/2017\/11\/28\/access-application-from-outside\/',
pageTitle: '如何从外部访问Kubernetes集群中的应用',
server: 'https:\/\/xxx.xxx.com',
site: 'xxx blog',
})
</script>
</div>
<div class="
col-lg-2 col-lg-offset-0
visible-lg-block
sidebar-container
catalog-container">
<div class="side-catalog">
<hr class="hidden-sm hidden-xs">
<h5>
<a class="catalog-toggle" href="#">CATALOG</a>
</h5>
<ul class="catalog-body"></ul>
</div>
</div>
<div class="
col-lg-8 col-lg-offset-2
col-md-10 col-md-offset-1
sidebar-container">
<section>
<hr class="hidden-sm hidden-xs">
<h5><a href="/tags/">FEATURED TAGS</a></h5>
<div class="tags">
<a href="/tags/docker" title="docker">
docker
</a>
<a href="/tags/istio" title="istio">
istio
</a>
<a href="/tags/kubernetes" title="kubernetes">
kubernetes
</a>
<a href="/tags/microservice" title="microservice">
microservice
</a>
<a href="/tags/security" title="security">
security
</a>
<a href="/tags/service-mesh" title="service mesh">
service mesh
</a>
<a href="/tags/tips" title="tips">
tips
</a>
</div>
</section>
<section>
<hr>
<h5>FRIENDS</h5>
<ul class="list-inline">
<li><a target="_blank" href="https://zhaozhihan.com">Linda的博客</a></li>
</ul>
</section>
</div>
</div>
</div>
</article>
<footer>
<div class="container">
<div class="row">
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
<ul class="list-inline text-center">
<li>
<a href="mailto:youremail@gmail.com">
<span class="fa-stack fa-lg">
<i class="fas fa-circle fa-stack-2x"></i>
<i class="fas fa-envelope fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
<li>
<a target="_blank" href="/your%20wechat%20qr%20code%20image">
<span class="fa-stack fa-lg">
<i class="fas fa-circle fa-stack-2x"></i>
<i class="fab fa-weixin fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
<li>
<a target="_blank" href="https://github.com/yourgithub">
<span class="fa-stack fa-lg">
<i class="fas fa-circle fa-stack-2x"></i>
<i class="fab fa-github fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
<li>
<a target="_blank" href="https://www.linkedin.com/in/yourlinkedinid">
<span class="fa-stack fa-lg">
<i class="fas fa-circle fa-stack-2x"></i>
<i class="fab fa-linkedin fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
<li>
<a target="_blank" href="https://stackoverflow.com/users/yourstackoverflowid">
<span class="fa-stack fa-lg">
<i class="fas fa-circle fa-stack-2x"></i>
<i class="fab fa-stack-overflow fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
<li>
<a href='' rel="alternate" type="application/rss+xml" title="David Young" >
<span class="fa-stack fa-lg">
<i class="fas fa-circle fa-stack-2x"></i>
<i class="fas fa-rss fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
</ul>
<p class="copyright text-muted">
Copyright &copy; David Young 2026
<br>
<a href="https://themes.gohugo.io/hugo-theme-cleanwhite">CleanWhite Hugo Theme</a> by <a href="https://zhaohuabing.com">Huabing</a> |
<iframe
style="margin-left: 2px; margin-bottom:-5px;"
frameborder="0" scrolling="0" width="100px" height="20px"
src="https://ghbtns.com/github-btn.html?user=zhaohuabing&repo=hugo-theme-cleanwhite&type=star&count=true" >
</iframe>
</p>
</div>
</div>
</div>
</footer>
<script>
function loadAsync(u, c) {
var d = document, t = 'script',
o = d.createElement(t),
s = d.getElementsByTagName(t)[0];
o.src = u;
if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }
s.parentNode.insertBefore(o, s);
}
</script>
<script>
if($('#tag_cloud').length !== 0){
loadAsync("/js/jquery.tagcloud.js",function(){
$.fn.tagcloud.defaults = {
color: {start: '#bbbbee', end: '#0085a1'},
};
$('#tag_cloud a').tagcloud();
})
}
</script>
<script>
(function() {
function updateTagcloudColors() {
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
const startColor = isDark ? '#808080' : '#bbbbee';
if($('#tag_cloud').length !== 0 && $.fn.tagcloud) {
$.fn.tagcloud.defaults = {
color: {start: startColor, end: '#0085a1'},
};
$('#tag_cloud a').tagcloud();
}
}
$(document).ready(function() {
updateTagcloudColors();
});
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.attributeName === 'data-theme') {
updateTagcloudColors();
}
});
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['data-theme']
});
})();
</script>
<script>
loadAsync("https://cdn.jsdelivr.net/npm/fastclick@1.0.6/lib/fastclick.min.js", function(){
var $nav = document.querySelector("nav");
if($nav) FastClick.attach($nav);
})
</script>
<script src="/js/theme-toggle.js"></script>
<script type="text/javascript">
function generateCatalog(selector) {
_containerSelector = 'div.post-container'
var P = $(_containerSelector), a, n, t, l, i, c;
a = P.find('h1,h2,h3,h4,h5,h6');
$(selector).html('')
a.each(function () {
n = $(this).prop('tagName').toLowerCase();
i = "#" + $(this).prop('id');
t = $(this).text();
c = $('<a href="' + i + '" rel="nofollow" title="' + t + '">' + t + '</a>');
l = $('<li class="' + n + '_nav"></li>').append(c);
$(selector).append(l);
});
return true;
}
generateCatalog(".catalog-body");
$(".catalog-toggle").click((function (e) {
e.preventDefault();
$('.side-catalog').toggleClass("fold")
}))
loadAsync("\/js\/jquery.nav.js", function () {
$('.catalog-body').onePageNav({
currentClass: "active",
changeHash: !1,
easing: "swing",
filter: "",
scrollSpeed: 700,
scrollOffset: 0,
scrollThreshold: .2,
begin: null,
end: null,
scrollChange: null,
padding: 80
});
});
</script>
</body>
</html>