971 lines
41 KiB
HTML
971 lines
41 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en-us">
|
||
<head><script src="/livereload.js?mindelay=10&v=2&port=1313&path=livereload" data-no-instant defer></script>
|
||
<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="http://localhost:1313/https://img.zhaohuabing.com/post-bg-2015.jpg">
|
||
<meta property="twitter:image" content="http://localhost:1313/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="http://localhost:1313/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的基本概念如docker,container,pod已有所了解。</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的消息,对于每一个Service,kube 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有三种类型,ClusterIP,NodePort和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 Balancer,Load 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="http://localhost:1313/"><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实现灰度发布(金丝雀发布)">←
|
||
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 →</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: 'http:\/\/localhost:1313\/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 © 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>
|