first commits

This commit is contained in:
David Young
2026-05-14 14:06:21 -06:00
parent d67dc1ad11
commit 015b3a8c5d
299 changed files with 87414 additions and 0 deletions

View File

@@ -0,0 +1,741 @@
<!DOCTYPE html>
<html lang="en-us">
<head><script src="/livereload.js?mindelay=10&amp;v=2&amp;port=1313&amp;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="Welcome to Zhaohuabing Blog" />
<meta property="og:title" content="Welcome to Zhaohuabing Blog" />
<meta property="twitter:title" content="Welcome to Zhaohuabing Blog" />
<meta name="description" content="Just About Everything">
<meta property="og:description" content="Just About Everything" />
<meta property="twitter:description" content="Just About Everything" />
<meta property="og:url" content="http://localhost:1313/2017/11/03/hello-world/" />
<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>Welcome to Zhaohuabing Blog | David Young Blog</title>
<link rel="canonical" href="/2017/11/03/hello-world/">
<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">
</div>
<h1>Welcome to Zhaohuabing Blog</h1>
<h2 class="subheading">Hello World, Hello Blog</h2>
<span class="meta">
Posted by
赵化冰
on
Saturday, November 4, 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">
<blockquote>
<p>“Yeah It&rsquo;s on. ”</p>
</blockquote>
<h2 id="hello-world">Hello World!</h2>
<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="next">
<a href="/2017/11/04/istio-install_and_example/" data-toggle="tooltip" data-placement="top" title="Istio及Bookinfo示例程序安装试用笔记">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: 'http:\/\/localhost:1313\/2017\/11\/03\/hello-world\/',
pageTitle: 'Welcome to Zhaohuabing Blog',
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>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,811 @@
<!DOCTYPE html>
<html lang="en-us">
<head><script src="/livereload.js?mindelay=10&amp;v=2&amp;port=1313&amp;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//img/istio-traffic-shifting/crossroads.png">
<meta property="twitter:image" content="http://localhost:1313//img/istio-traffic-shifting/crossroads.png" />
<meta name="title" content="使用Istio实现应用流量转移" />
<meta property="og:title" content="使用Istio实现应用流量转移" />
<meta property="twitter:title" content="使用Istio实现应用流量转移" />
<meta name="description" content="本任务将演示如何将应用流量逐渐从旧版本的服务迁移到新版本。通过Istio可以使用一系列不同权重的规则10%20%,··· 100%)将流量平缓地从旧版本服务迁移到新版本服务。">
<meta property="og:description" content="本任务将演示如何将应用流量逐渐从旧版本的服务迁移到新版本。通过Istio可以使用一系列不同权重的规则10%20%,··· 100%)将流量平缓地从旧版本服务迁移到新版本服务。" />
<meta property="twitter:description" content="本任务将演示如何将应用流量逐渐从旧版本的服务迁移到新版本。通过Istio可以使用一系列不同权重的规则10%20%,··· 100%)将流量平缓地从旧版本服务迁移到新版本服务。" />
<meta property="og:url" content="http://localhost:1313/2017/11/07/istio-traffic-shifting/" />
<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>使用Istio实现应用流量转移 | David Young Blog</title>
<link rel="canonical" href="/2017/11/07/istio-traffic-shifting/">
<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('/img/istio-traffic-shifting/crossroads.png')
}
</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/istio" title="Istio">
Istio
</a>
</div>
<h1>使用Istio实现应用流量转移</h1>
<h2 class="subheading">  &#34;本文翻译自istio官方文档&#34;</h2>
<span class="meta">
Posted by
    &#34;赵化冰&#34;
on
Tuesday, November 7, 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">
<p>关于Istio的更多内容请参考<a href="http://istio.doczh.cn/">istio中文文档</a></p>
<p>原文参见<a href="https://istio.io/docs/tasks/traffic-management/traffic-shifting.html">Traffic Shifting</a></p>
<p>本任务将演示如何将应用流量逐渐从旧版本的服务迁移到新版本。通过Istio可以使用一系列不同权重的规则10%20%,··· 100%)将流量平缓地从旧版本服务迁移到新版本服务。</p>
<p>为简单起见,本任务将采用两步将流量从<code>reviews:v1</code> 迁移到 <code>reviews:v3</code>权重分别为50%100%。</p>
<h1 id="开始之前">开始之前</h1>
<ul>
<li>
<p>参照文档<a href="http://istio.doczh.cn/docs/setup/kubernetes/index.html">安装指南</a>中的步骤安装Istio。</p>
</li>
<li>
<p>部署<a href="http://istio.doczh.cn/docs/guides/bookinfo.html">BookInfo</a> 示例应用程序。</p>
</li>
</ul>
<blockquote>
<p>请注意本文档假设示采用kubernetes部署示例应用程序。所有的示例命令行都采用规则yaml文件例如<code>samples/bookinfo/kube/route-rule-all-v1.yaml</code>指定的kubernetes版本。如果在不同的环境下运行本任务请将<code>kube</code>修改为运行环境中相应的目录例如对基于Consul的运行环境目录就是<code>samples/bookinfo/consul/route-rule-all-v1.yaml</code>)。</p>
</blockquote>
<h1 id="基于权重的版本路由">基于权重的版本路由</h1>
<ol>
<li>
<p>将所有微服务的缺省版本设置为v1.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>istioctl create -f samples/bookinfo/kube/route-rule-all-v1.yaml
</span></span></code></pre></div></li>
<li>
<p>在浏览器中打开http://$GATEWAY_URL/productpage, 确认<code>reviews</code> 服务目前的活动版本是v1。</p>
<p>可以看到浏览器中出现BooInfo应用的productpage页面。
注意<code>productpage</code>显示的评价内容不带星级。这是由于<code>reviews:v1</code>不会访问<code>ratings</code>服务。</p>
<blockquote>
<p>请注意:如果之前执行过 <a href="http://istio.doczh.cn/docs/tasks/traffic-management/request-routing.html">配置请求路由</a>任务则需要先注销测试用户“jason”或者删除之前单独为该用户创建的测试规则</p>
</blockquote>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>istioctl delete routerule reviews-test-v2
</span></span></code></pre></div></li>
<li>
<p>首先使用下面的命令把50%的流量从<code>reviews:v1</code>转移到<code>reviews:v3</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>istioctl replace -f samples/bookinfo/kube/route-rule-reviews-50-v3.yaml
</span></span></code></pre></div><p>注意这里使用了<code>istioctl replace</code>而不是<code>create</code></p>
</li>
<li>
<p>在浏览器中多次刷新<code>productpage</code>页面大约有50%的几率会看到页面中出现带红星的评价内容。</p>
<blockquote>
<p>请注意在目前的Envoy sidecar实现中可能需要刷新<code>productpage</code>很多次才能看到流量分发的效果。在看到页面出现变化前有可能需要刷新15次或者更多。如果修改规则将90%的流量路由到v3可以看到更明显的效果。</p>
</blockquote>
</li>
<li>
<p>当v3版本的<code>reviews</code>服务已经稳定运行后可以将100%的流量路由到<code>reviews:v3</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>istioctl replace -f samples/bookinfo/kube/route-rule-reviews-v3.yaml
</span></span></code></pre></div><p>此时,以任何用户登录到<code>productpage</code>页面,都可以看到带红星的评价信息。</p>
</li>
</ol>
<h1 id="理解原理">理解原理</h1>
<p>在这个任务中我们使用了Istio的带权重路由的特性将流量从老版本的<code>reviews</code>服务逐渐迁移到了新版本服务中。</p>
<p>注意该方式和使用容器编排平台的部署特性来进行版本迁移是完全不同的。容器编排平台使用了实例scaling来对流量进行管理。而通过Istio两个版本的<code>reviews</code>服务可以独立地进行scale up和scale down并不会影响这两个版本服务之间的流量分发。</p>
<p>想了解更多支持scaling的按版本路由功能请查看<a href="https://istio.io/blog/canary-deployments-using-istio.html">Canary Deployments using Istio</a></p>
<h1 id="清理">清理</h1>
<ul>
<li>
<p>删除路由规则。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>istioctl delete -f samples/bookinfo/kube/route-rule-all-v1.yaml
</span></span></code></pre></div></li>
<li>
<p>如果不打算尝试后面的任务,请参照<a href="http://istio.doczh.cn/docs/guides/bookinfo.html#cleanup">BookInfo cleanup</a> 中的步骤关闭应用程序。</p>
</li>
</ul>
<h1 id="进阶阅读">进阶阅读</h1>
<ul>
<li>更多的内容请参见<a href="http://istio.doczh.cn/docs/concepts/traffic-management/rules-configuration.html">请求路由</a></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/04/istio-install_and_example/" data-toggle="tooltip" data-placement="top" title="Istio及Bookinfo示例程序安装试用笔记">&larr;
Previous Post</a>
</li>
<li class="next">
<a href="/2017/11/08/istio-canary-release/" data-toggle="tooltip" data-placement="top" title="采用Istio实现灰度发布(金丝雀发布)">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: 'http:\/\/localhost:1313\/2017\/11\/07\/istio-traffic-shifting\/',
pageTitle: '使用Istio实现应用流量转移',
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>

View File

@@ -0,0 +1,992 @@
<!DOCTYPE html>
<html lang="en-us">
<head><script src="/livereload.js?mindelay=10&amp;v=2&amp;port=1313&amp;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//img/istio-canary-release/canary_bg.jpg">
<meta property="twitter:image" content="http://localhost:1313//img/istio-canary-release/canary_bg.jpg" />
<meta name="title" content="采用Istio实现灰度发布(金丝雀发布)" />
<meta property="og:title" content="采用Istio实现灰度发布(金丝雀发布)" />
<meta property="twitter:title" content="采用Istio实现灰度发布(金丝雀发布)" />
<meta name="description" content="当应用上线以后运维面临的一大挑战是如何能在不影响已上线业务的情况下进行升级。本文将介绍如何使用Istio实现应用的灰度发布金丝雀发布">
<meta property="og:description" content="当应用上线以后运维面临的一大挑战是如何能在不影响已上线业务的情况下进行升级。本文将介绍如何使用Istio实现应用的灰度发布金丝雀发布" />
<meta property="twitter:description" content="当应用上线以后运维面临的一大挑战是如何能在不影响已上线业务的情况下进行升级。本文将介绍如何使用Istio实现应用的灰度发布金丝雀发布" />
<meta property="og:url" content="http://localhost:1313/2017/11/08/istio-canary-release/" />
<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>采用Istio实现灰度发布(金丝雀发布) | David Young Blog</title>
<link rel="canonical" href="/2017/11/08/istio-canary-release/">
<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>
<header class="intro-header" style="background-image: url('/img/home-bg-jeep.jpg')">
<div class="container">
<div class="row">
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1 ">
<div class="site-heading">
<h1>David Young </h1>
<span class="subheading">Bevonovo</span>
</div>
</div>
</div>
</div>
</header>
<article>
<div class="container">
<div class="row">
<div class="
col-lg-8 col-lg-offset-1
col-md-8 col-md-offset-1
col-sm-12
col-xs-12
post-container">
<h2 id="灰度发布又名金丝雀发布介绍">灰度发布(又名金丝雀发布)介绍</h2>
<p>当应用上线以后,运维面临的一大挑战是如何能够在不影响已上线业务的情况下进行升级。做过产品的同学都清楚,不管在发布前做过多么完备的自动化和人工测试,在发布后都会出现或多或少的故障。根据墨菲定律,可能会出错的版本发布一定会出错。</p>
<p>&ldquo;ANYTHING THAN CAN GO WRONG WILL GO WRONG&rdquo; &ndash;MURPHY&rsquo;S LAW</p>
<p>因此我们不能寄希望于在线下测试时发现所有潜在故障。在无法百分百避免版本升级故障的情况下,需要通过一种方式进行可控的版本发布,把故障影响控制在可以接受的范围内,并可以快速回退。</p>
<p>可以通过<a href="https://martinfowler.com/bliki/CanaryRelease.html">灰度发布(又名金丝雀发布)</a>来实现业务从老版本到新版本的平滑过渡,并避免升级过程中出现的问题对用户造成的影响。</p>
<p>“金丝雀发布”的来源于矿工们用金丝雀对矿井进行空气测试的做法。以前矿工挖煤的时候,矿工下矿井前会先把金丝雀放进去,或者挖煤的时候一直带着金丝雀。金丝雀对甲烷和一氧化碳浓度比较敏感,会先报警。所以大家都用“金丝雀”来搞最先的测试。</p>
<p>下图中左下方的少部分用户就被当作“金丝雀”来用于测试新上线的1.1版本。如果新版本出现问题,“金丝雀”们会报警,但不会影响其他用户业务的正常运行。
<img src="/img/istio-canary-release/canary-deployment.PNG" alt="Istio灰度发布示意图">
</p>
<p>灰度发布(金丝雀发布)的流程如下:</p>
<ul>
<li>准备和生产环境隔离的“金丝雀”服务器。</li>
<li>将新版本的服务部署到“金丝雀”服务器上。</li>
<li>对“金丝雀”服务器上的服务进行自动化和人工测试。</li>
<li>测试通过后,将“金丝雀”服务器连接到生产环境,将少量生产流量导入到“金丝雀”服务器中。</li>
<li>如果在线测试出现问题,则通过把生产流量从“金丝雀”服务器中重新路由到老版本的服务的方式进行回退,修复问题后重新进行发布。</li>
<li>如果在线测试顺利,则逐渐把生产流量按一定策略逐渐导入到新版本服务器中。</li>
<li>待新版本服务稳定运行后,删除老版本服务。</li>
</ul>
<h2 id="istio实现灰度发布金丝雀发布的原理">Istio实现灰度发布(金丝雀发布)的原理</h2>
<p>从上面的流程可以看到,如果要实现一套灰度发布的流程,需要应用程序和运维流程对该发布过程进行支持,工作量和难度的挑战是非常大的。虽然面对的问题类似,但每个企业或组织一般采用不同的私有化实现方案来进行灰度发布,为解决该问题导致研发和运维花费了大量的成本。</p>
<p>Istio通过高度的抽象和良好的设计采用一致的方式解决了该问题采用sidecar对应用流量进行了转发通过Pilot下发路由规则可以在不修改应用程序的前提下实现应用的灰度发布。</p>
<p>备注采用kubernetes的<a href="https://kubernetes.io/docs/tasks/run-application/rolling-update-replication-controller/">滚动升级(rolling update)</a>功能也可以实现不中断业务的应用升级,但滚动升级是通过逐渐使用新版本的服务来替换老版本服务的方式对应用进行升级,在滚动升级不能对应用的流量分发进行控制,因此无法采用受控地把生产流量逐渐导流到新版本服务中,也就无法控制服务升级对用户造成的影响。</p>
<p>采用Istio后可以通过定制路由规则将特定的流量如指定特征的用户导入新版本服务中在生产环境下进行测试同时通过渐进受控地导入生产流量可以最小化升级中出现的故障对用户的影响。并且在同时存在新老版本服务时还可根据应用压力对不同版本的服务进行独立的缩扩容非常灵活。采用Istio进行灰度发布的流程如下图所示
<img src="/img/istio-canary-release/canary-deployments.gif" alt="Istio灰度发布示意图">
</p>
<h2 id="操作步骤">操作步骤</h2>
<p>下面采用Istion自带的BookinfoInfo示例程序来试验灰度发布的流程。</p>
<h3 id="测试环境安装">测试环境安装</h3>
<p>首先参考<a href="http://zhaohuabing.com/2017/11/04/istio-install_and_example/">手把手教你从零搭建Istio及Bookinfo示例程序</a>安装Kubernetes及Istio控制面。</p>
<p>因为本试验并不需要安装全部3个版本的reviews服务因此如果已经安装了该应用先采用下面的命令卸载。</p>
<pre tabindex="0"><code>istio-0.2.10/samples/bookinfo/kube/cleanup.sh
</code></pre><h3 id="部署v1版本的服务">部署V1版本的服务</h3>
<p>首先只部署V1版本的Bookinfo应用程序。由于示例中的yaml文件中包含了3个版本的reviews服务我们先将V2和V3版本的Deployment从yaml文件istio-0.2.10/samples/bookinfo/kube/bookinfo.yaml中删除。</p>
<p>从Bookinfo.yaml中删除这部分内容:</p>
<pre tabindex="0"><code>apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: reviews-v2
spec:
replicas: 1
template:
metadata:
labels:
app: reviews
version: v2
spec:
containers:
- name: reviews
image: istio/examples-bookinfo-reviews-v2:0.2.3
imagePullPolicy: IfNotPresent
ports:
- containerPort: 9080
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: reviews-v3
spec:
replicas: 1
template:
metadata:
labels:
app: reviews
version: v3
spec:
containers:
- name: reviews
image: istio/examples-bookinfo-reviews-v3:0.2.3
imagePullPolicy: IfNotPresent
ports:
- containerPort: 9080
---
</code></pre><p>部署V1版本的Bookinfo程序。</p>
<pre tabindex="0"><code>kubectl apply -f &lt;(istioctl kube-inject -f istio-0.2.10/samples/bookinfo/kube/bookinfo.yaml)
</code></pre><p>通过kubectl命令行确认pod部署可以看到只有V1版本的服务。</p>
<pre tabindex="0"><code>kubectl get pods
NAME READY STATUS RESTARTS AGE
details-v1-3688945616-nhkqk 2/2 Running 0 2m
productpage-v1-2055622944-m3fql 2/2 Running 0 2m
ratings-v1-233971408-0f3s9 2/2 Running 0 2m
reviews-v1-1360980140-0zs9z 2/2 Running 0 2m
</code></pre><p>在浏览器中打开应用程序页面地址为istio-ingress的External IP。由于V1版本的reviews服务并不会调用rating服务因此可以看到Product 页面显示的是不带星级的评价信息。</p>
<p><code>http://10.12.25.116/productpage</code><br>
<img src="//img/istio-canary-release/product-page-default.PNG" alt="">
</p>
<p>此时系统中微服务的部署情况如下图所示下面的示意图均忽略和本例关系不大的details和ratings服务
<img src="//img/istio-canary-release/canary-example-only-v1.PNG" alt="">
</p>
<h3 id="部署v2版本的reviews服务">部署V2版本的reviews服务</h3>
<p>在部署V2版本的reviews服务前需要先创建一条缺省路由规则route-rule-default-reviews.yaml将所有生产流量都导向V1版本避免对线上用户的影响。</p>
<pre tabindex="0"><code>apiVersion: config.istio.io/v1alpha2
kind: RouteRule
metadata:
name: reviews-default
spec:
destination:
name: reviews
precedence: 1
route:
- labels:
version: v1
</code></pre><p>启用该路由规则。</p>
<pre tabindex="0"><code>istioctl create -f route-rule-default-reviews.yaml -n default
</code></pre><p>创建一个V2版本的部署文件bookinfo-reviews-v2.yaml内容如下</p>
<pre tabindex="0"><code>apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: reviews-v2
spec:
replicas: 1
template:
metadata:
labels:
app: reviews
version: v2
spec:
containers:
- name: reviews
image: istio/examples-bookinfo-reviews-v2:0.2.3
imagePullPolicy: IfNotPresent
ports:
- containerPort: 9080
</code></pre><p>部署V2版本的reviews服务。</p>
<pre tabindex="0"><code>kubectl apply -f &lt;(istioctl kube-inject -f bookinfo-reviews-v2.yaml)
</code></pre><p>此时系统中部署了V1和V2两个版本的reviews服务但所有的业务流量都被规则reviews-default导向了V1如下图所示
<img src="/img/istio-canary-release/canary-example-deploy-v2.PNG" alt="">
</p>
<h3 id="将测试流量导入到v2版本的reviews服务">将测试流量导入到V2版本的reviews服务</h3>
<p>在进行模拟测试时,由于测试环境和生产环境的网络,服务器,操作系统等环境存在差异,很难完全模拟生产环境进行测试。为了减少环境因素的对测试结果的影响,我们希望能在生产环境中进行上线前的测试,但如果没有很好的隔离措施,可能会导致测试影响已上线的业务,对企业造成损失。</p>
<p>通过采用Istio的路由规则可以在类生产环境中进行测试又完全隔离了线上用户的生产流量和测试流量最小化模拟测试对已上线业务的影响。如下图所示
<img src="/img/istio-canary-release/canary-example-route-test.PNG" alt="">
</p>
<p>创建一条规则,将用户名为 test-user 的流量导入到V2</p>
<pre tabindex="0"><code>apiVersion: config.istio.io/v1alpha2
kind: RouteRule
metadata:
name: reviews-test-user
spec:
destination:
name: reviews
precedence: 2
match:
request:
headers:
cookie:
regex: &#34;^(.*?;)?(user=test-user)(;.*)?$&#34;
route:
- labels:
version: v2
</code></pre><p>注意precedence属性用于设置规则的优先级在同时存在多条规则的情况下优先级高的规则将先执行。这条规则的precedence设置为2以确保其在缺省规则之前运行将test-user用户的请求导流到V2版本reviews服务中。</p>
<p>启用该规则。</p>
<pre tabindex="0"><code>istioctl create -f route-rule-test-reviews-v2.yaml -n default
</code></pre><p>以test-user用户登录可以看到V2版本带星级的评价页面。
<img src="/img/istio-canary-release/product-page-test-user.PNG" alt="">
</p>
<p>注销test-user只能看到V1版本不带星级的评价页面。如下图所示
<img src="/img/istio-canary-release/product-page-default.PNG" alt="">
</p>
<h3 id="将部分生产流量导入到v2版本的reviews服务">将部分生产流量导入到V2版本的reviews服务</h3>
<p>在线上模拟测试完成后如果系统测试情况良好可以通过规则将一部分用户流量导入到V2版本的服务中进行小规模的“金丝雀”测试。</p>
<p>修改规则route-rule-default-reviews.yaml将50%的流量导入V2版本。</p>
<blockquote>
<p>备注本例只是描述原理因此为简单起见将50%流量导入V2版本在实际操作中更可能是先导入较少流量然后根据监控的新版本运行情况将流量逐渐导入如采用5%10%20%50% &hellip;的比例逐渐导入。</p>
</blockquote>
<pre tabindex="0"><code>apiVersion: config.istio.io/v1alpha2
kind: RouteRule
metadata:
name: reviews-default
spec:
destination:
name: reviews
precedence: 1
route:
- labels:
version: v1
weight: 50
- labels:
version: v2
weight: 50
</code></pre><pre tabindex="0"><code>istioctl replace -f route-rule-default-reviews.yaml -n default
</code></pre><p>此时系统部署如下图所示:
<img src="/img/istio-canary-release/canary-example-route-production-50.PNG" alt="">
</p>
<h3 id="将所有生产流量导入到到v2版本的reviews服务">将所有生产流量导入到到V2版本的reviews服务</h3>
<p>如果新版本的服务运行正常则可以将所有流量导入到V2版本。</p>
<pre tabindex="0"><code>apiVersion: config.istio.io/v1alpha2
kind: RouteRule
metadata:
name: reviews-default
spec:
destination:
name: reviews
precedence: 1
route:
- labels:
version: v2
weight: 100
</code></pre><pre tabindex="0"><code>istioctl replace -f route-rule-default-reviews.yaml -n default
</code></pre><p>系统部署如下图所示:
<img src="/img/istio-canary-release/canary-example-route-production-100.PNG" alt="">
</p>
<p>此时不管以任何用户登录都只能看到V2版本带星级的评价页面如下图所示
<img src="/img/istio-canary-release/product-page-default-v2.PNG" alt="">
</p>
<blockquote>
<p>备注如果灰度发布的过程中新版本的服务出现问题则可以通过修改路由规则将流量重新导入到V1版本的服务中将V2版本故障修复后再进行测试。</p>
</blockquote>
<h3 id="删除v1版本的reviews服务">删除V1版本的reviews服务</h3>
<p>待V2版本上线稳定运行后删除V1版本的reviews服务和测试规则。</p>
<pre tabindex="0"><code>kubectl delete pod reviews-v1-1360980140-0zs9z
istioctl delete -f route-rule-test-reviews-v2.yaml -n default
</code></pre><h2 id="参考">参考</h2>
<ul>
<li><a href="https://istio.io/docs/">Istio官方文档</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\/08\/istio-canary-release\/',
pageTitle: '采用Istio实现灰度发布(金丝雀发布)',
server: 'https:\/\/xxx.xxx.com',
site: 'xxx blog',
})
</script>
</div>
<div class="
col-lg-3 col-lg-offset-0
col-md-3 col-md-offset-0
col-sm-12
col-xs-12
sidebar-container
">
<section class="visible-md visible-lg">
<div class="short-about">
<a href="/about">
<img src="/img/zhaohuabing.png" alt="avatar" style="cursor: pointer" />
</a>
<p>Open Source Enthusiast</p>
<ul class="list-inline">
<li>
<a href="mailto:youremail@gmail.com">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa 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>
</ul>
</div>
</section>
<section>
<hr class="hidden-sm hidden-xs">
<h5>FEATURED TAGS</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 class="hidden-sm hidden-xs">
<h5>FRIENDS</h5>
<ul class="list-inline">
<li><a target="_blank" href="https://zhaozhihan.com">Linda的博客</a></li>
</ul>
</section>
<section>
<hr class="hidden-sm hidden-xs">
<h5>LAST POSTS</h5>
<ul>
<li><a href="/2025/07/06/mathematical-formulae/">Authoring mathematical formulae</a></li>
<li><a href="/post/readme/">Clean White Theme for Hugo</a></li>
<li><a href="/2018/06/04/introducing-the-istio-v1alpha3-routing-api/">Istio v1aplha3 routing API介绍(译文)</a></li>
<li><a href="/2018/06/02/istio08/">Istio 0.8 Release发布</a></li>
<li><a href="/2018/05/24/set_up_my_ubuntu_desktop/">Everything about Setting Up My Ubuntu Desktop</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>

View File

@@ -0,0 +1,970 @@
<!DOCTYPE html>
<html lang="en-us">
<head><script src="/livereload.js?mindelay=10&amp;v=2&amp;port=1313&amp;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的基本概念如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="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实现灰度发布(金丝雀发布)">&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: '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 &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>